From 92bf413d684d8c3aea0e00833a515a82f7070e2e Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 31 Mar 2017 10:02:40 +0200 Subject: [PATCH 001/721] [fix] Remove version from api --- bin/yunohost-api | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/yunohost-api b/bin/yunohost-api index d2b219f8b..054d5df84 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -192,12 +192,10 @@ if __name__ == '__main__': _init_moulinette(opts.use_websocket, opts.debug, opts.verbose) # Run the server - from yunohost.utils.packages import ynh_packages_version ret = moulinette.api( _retrieve_namespaces(), host=opts.host, port=opts.port, routes={ ('GET', '/installed'): is_installed, - ('GET', '/version'): ynh_packages_version, }, use_cache=opts.use_cache, use_websocket=opts.use_websocket ) sys.exit(ret) From 35d4a1001fcd04207a9341389b5614e55919229d Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Sat, 2 Sep 2017 18:27:25 +0200 Subject: [PATCH 002/721] Add fail2ban helpers --- data/helpers.d/backend | 54 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index c54e82754..0795be38e 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -183,3 +183,57 @@ ynh_remove_fpm_config () { ynh_secure_remove "/etc/php5/fpm/conf.d/20-$app.ini" 2>&1 sudo systemctl reload php5-fpm } + +# Create a dedicated fail2ban config (jail and filter conf files) +# +# usage: ynh_add_fail2ban_config log_file filter [max_retry [ports]] +# | arg: log_file - Log file to be checked by fail2ban +# | arg: failregex - Failregex to be looked for by fail2ban +# | arg: max_retry - Maximum number of retries allowed before banning IP address - default: 3 +# | arg: ports - Ports blocked for a banned IP address - default: http,https +ynh_add_fail2ban_config () { + # Process parameters + logpath=$1 + failregex=$2 + max_retry=${3:-3} + ports=${4:-http,https} + + test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." + test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." + + finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" + finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" + ynh_backup_if_checksum_is_different "$finalfail2banjailconf" 1 + ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" 1 + + sudo tee $finalfail2banjailconf < Date: Sun, 29 Apr 2018 17:43:13 +0200 Subject: [PATCH 003/721] Update ynh_system_user_create helper to allow creating users with login shell --- data/helpers.d/user | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index 47e6eb88a..e59466b33 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -41,9 +41,10 @@ ynh_system_user_exists() { # Create a system user # -# usage: ynh_system_user_create user_name [home_dir] +# usage: ynh_system_user_create user_name [home_dir [use_shell]] # | arg: user_name - Name of the system user that will be create # | arg: home_dir - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home +# | arg: use_shell - Create a user using the default login shell if present. If this argument is omitted, the user will be created with /usr/sbin/nologin shell ynh_system_user_create () { if ! ynh_system_user_exists "$1" # Check if the user exists on the system then # If the user doesn't exist @@ -52,7 +53,12 @@ ynh_system_user_create () { else local user_home_dir="--no-create-home" fi - sudo useradd $user_home_dir --system --user-group $1 --shell /usr/sbin/nologin || ynh_die "Unable to create $1 system account" + if [ $# -ge 3 ]; then # If we want a shell for the user + local shell="" # Use default shell + else + local shell="--shell /usr/sbin/nologin" + fi + useradd $user_home_dir --system --user-group $1 $shell || ynh_die "Unable to create $1 system account" fi } From 24449a500433bcfdbb792ca262fd7592437109f9 Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Tue, 8 May 2018 09:21:41 +0200 Subject: [PATCH 004/721] Update ynh_system_user_create comment with explicit examples --- data/helpers.d/user | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index e59466b33..43e4902b3 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -41,10 +41,18 @@ ynh_system_user_exists() { # Create a system user # +# examples: +# - ynh_system_user_create nextcloud -> creates a nextcloud user with +# no home directory and /usr/sbin/nologin login shell (hence no login capability) +# - ynh_system_user_create discourse /var/www/discourse 1 --> creates a +# discourse user using /var/www/discourse as home directory and the default login shell +# # usage: ynh_system_user_create user_name [home_dir [use_shell]] # | arg: user_name - Name of the system user that will be create -# | arg: home_dir - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home -# | arg: use_shell - Create a user using the default login shell if present. If this argument is omitted, the user will be created with /usr/sbin/nologin shell +# | arg: home_dir - Path of the home dir for the user. Usually the final path +# of the app. If this argument is omitted, the user will be created without home +# | arg: use_shell - Create a user using the default login shell if present. +# If this argument is omitted, the user will be created with /usr/sbin/nologin shell ynh_system_user_create () { if ! ynh_system_user_exists "$1" # Check if the user exists on the system then # If the user doesn't exist From 561f1aecb0f18979233f4bacdc8f87088da47c28 Mon Sep 17 00:00:00 2001 From: Jimmy Monin Date: Sat, 19 May 2018 10:11:16 +0200 Subject: [PATCH 005/721] Update to latest version from Experimental_helpers --- data/helpers.d/backend | 44 ++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 0795be38e..e14095102 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -192,41 +192,47 @@ ynh_remove_fpm_config () { # | arg: max_retry - Maximum number of retries allowed before banning IP address - default: 3 # | arg: ports - Ports blocked for a banned IP address - default: http,https ynh_add_fail2ban_config () { - # Process parameters - logpath=$1 - failregex=$2 - max_retry=${3:-3} - ports=${4:-http,https} - - test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." - test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." - + # Process parameters + logpath=$1 + failregex=$2 + max_retry=${3:-3} + ports=${4:-http,https} + + test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." + test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." + finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" ynh_backup_if_checksum_is_different "$finalfail2banjailconf" 1 ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" 1 - - sudo tee $finalfail2banjailconf <&2 + echo "WARNING${fail2ban_error#*WARNING}" >&2 + fi } # Remove the dedicated fail2ban config (jail and filter conf files) @@ -234,6 +240,6 @@ EOF # usage: ynh_remove_fail2ban_config ynh_remove_fail2ban_config () { ynh_secure_remove "/etc/fail2ban/jail.d/$app.conf" - ynh_secure_remove "/etc/fail2ban/filter.d/$app.conf" - sudo systemctl restart fail2ban -} \ No newline at end of file + ynh_secure_remove "/etc/fail2ban/filter.d/$app.conf" + systemctl reload fail2ban +} From 7752bc0fb7eab0811926c891be5bb9703cd3b23a Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 27 Aug 2018 21:51:36 +0200 Subject: [PATCH 006/721] Update fail2ban helpers from experimental helpers. --- data/helpers.d/backend | 73 +++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index e14095102..4e939331e 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -187,26 +187,31 @@ ynh_remove_fpm_config () { # Create a dedicated fail2ban config (jail and filter conf files) # # usage: ynh_add_fail2ban_config log_file filter [max_retry [ports]] -# | arg: log_file - Log file to be checked by fail2ban -# | arg: failregex - Failregex to be looked for by fail2ban -# | arg: max_retry - Maximum number of retries allowed before banning IP address - default: 3 -# | arg: ports - Ports blocked for a banned IP address - default: http,https +# | arg: -l, --logpath= - Log file to be checked by fail2ban +# | arg: -r, --failregex= - Failregex to be looked for by fail2ban +# | arg: -m, --max_retry= - Maximum number of retries allowed before banning IP address - default: 3 +# | arg: -p, --ports= - Ports blocked for a banned IP address - default: http,https ynh_add_fail2ban_config () { - # Process parameters - logpath=$1 - failregex=$2 - max_retry=${3:-3} - ports=${4:-http,https} - - test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." - test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." + # Declare an array to define the options of this helper. + declare -Ar args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= ) + local logpath + local failregex + local max_retry + local ports + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + max_retry=${max_retry:-3} + ports=${ports:-http,https} - finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" - finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" - ynh_backup_if_checksum_is_different "$finalfail2banjailconf" 1 - ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" 1 + test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." + test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." - tee $finalfail2banjailconf <&2 - echo "WARNING${fail2ban_error#*WARNING}" >&2 - fi + if [ "$(lsb_release --codename --short)" != "jessie" ]; then + systemctl reload fail2ban + else + systemctl restart fail2ban + fi + local fail2ban_error="$(journalctl -u fail2ban | tail -n50 | grep "WARNING.*$app.*")" + if [ -n "$fail2ban_error" ] + then + echo "[ERR] Fail2ban failed to load the jail for $app" >&2 + echo "WARNING${fail2ban_error#*WARNING}" >&2 + fi } # Remove the dedicated fail2ban config (jail and filter conf files) # # usage: ynh_remove_fail2ban_config ynh_remove_fail2ban_config () { - ynh_secure_remove "/etc/fail2ban/jail.d/$app.conf" - ynh_secure_remove "/etc/fail2ban/filter.d/$app.conf" - systemctl reload fail2ban + ynh_secure_remove "/etc/fail2ban/jail.d/$app.conf" + ynh_secure_remove "/etc/fail2ban/filter.d/$app.conf" + if [ "$(lsb_release --codename --short)" != "jessie" ]; then + systemctl reload fail2ban + else + systemctl restart fail2ban + fi } From 0fd9e179f16a92af63292e9e8b048623fda5ac2e Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 28 Aug 2018 00:14:37 +0200 Subject: [PATCH 007/721] Add ynh_check_app_version_changed - Add the new helper ynh_check_app_version_changed to check the version before an upgrade - Add also the helpers ynh_read_manifest, ynh_app_upstream_version and ynh_app_package_version. - These previous helper have been modified (from the experimental version) to support getopts - ynh_check_app_version_changed has been modified to use ynh_app_upstream_version --- data/helpers.d/system | 105 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/data/helpers.d/system b/data/helpers.d/system index 70cc57493..63b90ef13 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -53,3 +53,108 @@ ynh_abort_if_errors () { ynh_get_debian_release () { echo $(lsb_release --codename --short) } + +# Read the value of a key in a ynh manifest file +# +# usage: ynh_read_manifest manifest key +# | arg: -m, --manifest= - Path of the manifest to read +# | arg: -k, --key= - Name of the key to find +ynh_read_manifest () { + # Declare an array to define the options of this helper. + declare -Ar args_array=( [m]=manifest= [k]=manifest_key= ) + local manifest + local manifest_key + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + python3 -c "import sys, json;print(json.load(open('$manifest', encoding='utf-8'))['$manifest_key'])" +} + +# Read the upstream version from the manifest +# The version number in the manifest is defined by ~ynh +# For example : 4.3-2~ynh3 +# This include the number before ~ynh +# In the last example it return 4.3-2 +# +# usage: ynh_app_upstream_version [-m manifest] +# | arg: -m, --manifest= - Path of the manifest to read +ynh_app_upstream_version () { + declare -Ar args_array=( [m]=manifest= ) + local manifest + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + manifest="${manifest:-../manifest.json}" + if [ ! -e "$manifest_path" ]; then + manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place + fi + version_key=$(ynh_read_manifest --manifest="$manifest_path" --manifest_key="version") + echo "${version_key/~ynh*/}" +} + +# Read package version from the manifest +# The version number in the manifest is defined by ~ynh +# For example : 4.3-2~ynh3 +# This include the number after ~ynh +# In the last example it return 3 +# +# usage: ynh_app_package_version [-m manifest] +# | arg: -m, --manifest= - Path of the manifest to read +ynh_app_package_version () { + declare -Ar args_array=( [m]=manifest= ) + local manifest + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + manifest="${manifest:-../manifest.json}" + if [ ! -e "$manifest_path" ]; then + manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place + fi + version_key=$(ynh_read_manifest --manifest="$manifest_path" --manifest_key="version") + echo "${version_key/*~ynh/}" +} + +# Checks the app version to upgrade with the existing app version and returns: +# - UPGRADE_APP if the upstream app version has changed +# - UPGRADE_PACKAGE if only the YunoHost package has changed +# +## It stops the current script without error if the package is up-to-date +# +# This helper should be used to avoid an upgrade of an app, or the upstream part +# of it, when it's not needed +# +# To force an upgrade, even if the package is up to date, +# you have to set the variable YNH_FORCE_UPGRADE before. +# example: sudo YNH_FORCE_UPGRADE=1 yunohost app upgrade MyApp + +# usage: ynh_check_app_version_changed +ynh_check_app_version_changed () { + local force_upgrade=${YNH_FORCE_UPGRADE:-0} + local package_check=${PACKAGE_CHECK_EXEC:-0} + + # By default, upstream app version has changed + local return_value="UPGRADE_APP" + + local current_version=$(ynh_read_manifest --manifest="/etc/yunohost/apps/$YNH_APP_INSTANCE_NAME/manifest.json" --manifest_key="version" || echo 1.0) + local current_upstream_version="$(ynh_app_upstream_version --manifest="/etc/yunohost/apps/$YNH_APP_INSTANCE_NAME/manifest.json")" + local update_version=$(ynh_read_manifest --manifest="../manifest.json" --manifest_key="version" || echo 1.0) + local update_upstream_version="$(ynh_app_upstream_version)" + + if [ "$current_version" == "$update_version" ] ; then + # Complete versions are the same + if [ "$force_upgrade" != "0" ] + then + echo "Upgrade forced by YNH_FORCE_UPGRADE." >&2 + unset YNH_FORCE_UPGRADE + elif [ "$package_check" != "0" ] + then + echo "Upgrade forced for package check." >&2 + else + ynh_die "Up-to-date, nothing to do" 0 + fi + elif [ "$current_upstream_version" == "$update_upstream_version" ] ; then + # Upstream versions are the same, only YunoHost package versions differ + return_value="UPGRADE_PACKAGE" + fi + echo $return_value +} From e0533a1a6a48bbce12fe8a961647f62436fa3649 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 28 Aug 2018 00:29:24 +0200 Subject: [PATCH 008/721] Really important contribution to the PR :ninja: --- data/helpers.d/system | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index 63b90ef13..f09343953 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -126,7 +126,7 @@ ynh_app_package_version () { # To force an upgrade, even if the package is up to date, # you have to set the variable YNH_FORCE_UPGRADE before. # example: sudo YNH_FORCE_UPGRADE=1 yunohost app upgrade MyApp - +# # usage: ynh_check_app_version_changed ynh_check_app_version_changed () { local force_upgrade=${YNH_FORCE_UPGRADE:-0} From 62c8f577c9f57e9164a7bbbaffd5a5a611113daa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Aug 2018 13:25:29 +0200 Subject: [PATCH 009/721] Remove old sudo --- data/helpers.d/backend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 4e939331e..bc3ae6a7d 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -211,7 +211,7 @@ ynh_add_fail2ban_config () { ynh_backup_if_checksum_is_different "$finalfail2banjailconf" 1 ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" 1 - sudo tee $finalfail2banjailconf < Date: Wed, 29 Aug 2018 13:27:25 +0200 Subject: [PATCH 010/721] Remove sudo --- data/helpers.d/backend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index bc3ae6a7d..6b5ca1d37 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -220,7 +220,7 @@ logpath = $logpath maxretry = $max_retry EOF - sudo tee $finalfail2banfilterconf < Date: Sun, 26 Aug 2018 12:46:42 +0200 Subject: [PATCH 011/721] Add return value in hook_exec --- src/yunohost/hook.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 87844ce17..143742df1 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -280,8 +280,8 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None, try: hook_args = pre_callback(name=name, priority=priority, path=path, args=args) - hook_exec(path, args=hook_args, chdir=chdir, env=env, - no_trace=no_trace, raise_on_error=True, user="root") + hook_return = hook_exec(path, args=hook_args, chdir=chdir, env=env, + no_trace=no_trace, raise_on_error=True, user="root")[1] except MoulinetteError as e: state = 'failed' logger.error(e.strerror, exc_info=1) @@ -294,6 +294,10 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None, result[state][name].append(path) except KeyError: result[state][name] = [path] + try: + result['stdreturn'].append(hook_return) + except KeyError: + result['stdreturn'] = [hook_return] return result @@ -341,6 +345,11 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, stdinfo = os.path.join(tempfile.mkdtemp(), "stdinfo") env['YNH_STDINFO'] = stdinfo + stdreturn = os.path.join(tempfile.mkdtemp(), "stdreturn") + with open(stdreturn, 'w') as f: + f.write('') + env['YNH_STDRETURN'] = stdreturn + # Construct command to execute if user == "root": command = ['sh', '-c'] @@ -388,11 +397,18 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, errno.EIO, m18n.n('hook_exec_not_terminated', path=path)) else: logger.error(m18n.n('hook_exec_not_terminated', path=path)) - return 1 + return 1, '' elif raise_on_error and returncode != 0: raise MoulinetteError( errno.EIO, m18n.n('hook_exec_failed', path=path)) - return returncode + + with open(stdreturn, 'r') as f: + returnstring = f.read() + stdreturndir = os.path.split(stdreturn)[0] + os.remove(stdreturn) + os.rmdir(stdreturndir) + + return returncode, returnstring def _extract_filename_parts(filename): From b64196c47ddc80926c4d46717da915bb78833a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 26 Aug 2018 12:31:20 +0200 Subject: [PATCH 012/721] Change return of hook_exec everywhere --- src/yunohost/app.py | 12 ++++++------ src/yunohost/backup.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 1fed09425..bad1797b3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -513,7 +513,7 @@ def app_change_url(operation_logger, auth, app, domain, path): os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url"))) if hook_exec(os.path.join(APP_TMP_FOLDER, 'scripts/change_url'), - args=args_list, env=env_dict, user="root") != 0: + args=args_list, env=env_dict, user="root")[0] != 0: msg = "Failed to change '%s' url." % app logger.error(msg) operation_logger.error(msg) @@ -640,7 +640,7 @@ def app_upgrade(auth, app=[], url=None, file=None): # Execute App upgrade script os.system('chown -hR admin: %s' % INSTALL_TMP) if hook_exec(extracted_app_folder + '/scripts/upgrade', - args=args_list, env=env_dict, user="root") != 0: + args=args_list, env=env_dict, user="root")[0] != 0: msg = m18n.n('app_upgrade_failed', app=app_instance_name) logger.error(msg) operation_logger.error(msg) @@ -801,7 +801,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on install_retcode = hook_exec( os.path.join(extracted_app_folder, 'scripts/install'), args=args_list, env=env_dict, user="root" - ) + )[0] except (KeyboardInterrupt, EOFError): install_retcode = -1 except: @@ -825,7 +825,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on remove_retcode = hook_exec( os.path.join(extracted_app_folder, 'scripts/remove'), args=[app_instance_name], env=env_dict_remove, user="root" - ) + )[0] if remove_retcode != 0: msg = m18n.n('app_not_properly_removed', app=app_instance_name) @@ -912,7 +912,7 @@ def app_remove(operation_logger, auth, app): operation_logger.flush() if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list, - env=env_dict, user="root") == 0: + env=env_dict, user="root")[0] == 0: logger.success(m18n.n('app_removed', app=app)) hook_callback('post_app_remove', args=args_list, env=env_dict) @@ -1518,7 +1518,7 @@ def app_action_run(app_id, action, args=None): env=env_dict, chdir=cwd, user=action_declaration.get("user", "root"), - ) + )[0] if retcode not in action_declaration.get("accepted_return_codes", [0]): raise MoulinetteError(retcode, "Error while executing action '%s' of app '%s': return code %s" % (action, app_id, retcode)) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 88959cc2f..bad2f53c2 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -680,7 +680,7 @@ class BackupManager(): subprocess.call(['install', '-Dm555', app_script, tmp_script]) hook_exec(tmp_script, args=[tmp_app_bkp_dir, app], - raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict, user="root") + raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict, user="root")[0] self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"]) except: @@ -1311,7 +1311,7 @@ class RestoreManager(): chdir=app_backup_in_archive, raise_on_error=True, env=env_dict, - user="root") + user="root")[0] except: msg = m18n.n('restore_app_failed',app=app_instance_name) logger.exception(msg) @@ -1336,7 +1336,7 @@ class RestoreManager(): # Execute remove script # TODO: call app_remove instead if hook_exec(remove_script, args=[app_instance_name], - env=env_dict_remove, user="root") != 0: + env=env_dict_remove, user="root")[0] != 0: msg = m18n.n('app_not_properly_removed', app=app_instance_name) logger.warning(msg) operation_logger.error(msg) From b7554dec2176f38df20a0d49a42d522a7c702ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 26 Aug 2018 14:59:26 +0200 Subject: [PATCH 013/721] Use json for return --- locales/en.json | 1 + src/yunohost/hook.py | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/locales/en.json b/locales/en.json index 074512311..1e7e78490 100644 --- a/locales/en.json +++ b/locales/en.json @@ -199,6 +199,7 @@ "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", + "hook_json_return_error": "Faild to read return from hook {path:s}. Error: {msg:s}", "hook_list_by_invalid": "Invalid property to list hook by", "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation complete", diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 143742df1..3ef05980a 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -32,6 +32,7 @@ from glob import iglob from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils import log +from moulinette.utils.filesystem import read_json HOOK_FOLDER = '/usr/share/yunohost/hooks/' CUSTOM_HOOK_FOLDER = '/etc/yunohost/hooks.d/' @@ -229,7 +230,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None, (name, priority, path, succeed) as arguments """ - result = {'succeed': {}, 'failed': {}} + result = {'succeed': {}, 'failed': {}, 'stdreturn' : []} hooks_dict = {} # Retrieve hooks @@ -294,10 +295,13 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None, result[state][name].append(path) except KeyError: result[state][name] = [path] - try: - result['stdreturn'].append(hook_return) - except KeyError: - result['stdreturn'] = [hook_return] + + #print(hook_return) + #for r in hook_return.: + result['stdreturn'].extend(hook_return) #for r in hook_return + #print(r) + + #print(result['stdreturn']) return result @@ -402,13 +406,17 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, raise MoulinetteError( errno.EIO, m18n.n('hook_exec_failed', path=path)) - with open(stdreturn, 'r') as f: - returnstring = f.read() + try: + returnjson = read_json(stdreturn) + except Exception as e: + returnjson = {} + errno.EIO, m18n.n('hook_json_return_error', path=path, msg=str(e)) + stdreturndir = os.path.split(stdreturn)[0] os.remove(stdreturn) os.rmdir(stdreturndir) - return returncode, returnstring + return returncode, returnjson def _extract_filename_parts(filename): From 29bf70c57cd7ec24ac33d5688ccd75b2ac5bd101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 29 Aug 2018 18:54:39 +0200 Subject: [PATCH 014/721] Add hook name in return structure in hook_callback --- src/yunohost/hook.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 3ef05980a..efd14ca75 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -230,7 +230,6 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None, (name, priority, path, succeed) as arguments """ - result = {'succeed': {}, 'failed': {}, 'stdreturn' : []} hooks_dict = {} # Retrieve hooks @@ -292,16 +291,11 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None, post_callback(name=name, priority=priority, path=path, succeed=True) try: - result[state][name].append(path) + result[name][state].append(path) except KeyError: - result[state][name] = [path] + result[name][state] = [path] - #print(hook_return) - #for r in hook_return.: - result['stdreturn'].extend(hook_return) #for r in hook_return - #print(r) - - #print(result['stdreturn']) + result[name]['stdreturn'] = hook_return return result From e4e981c0fe4e635001619e3bd339aa5c32834954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 29 Aug 2018 20:56:00 +0200 Subject: [PATCH 015/721] Change struct returned by hook_callback --- src/yunohost/hook.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index efd14ca75..ce83ce011 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -230,6 +230,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None, (name, priority, path, succeed) as arguments """ + result = {} hooks_dict = {} # Retrieve hooks @@ -290,12 +291,8 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None, else: post_callback(name=name, priority=priority, path=path, succeed=True) - try: - result[name][state].append(path) - except KeyError: - result[name][state] = [path] - result[name]['stdreturn'] = hook_return + result[name] = {'path' : path, 'state' : state, 'stdreturn' : hook_return } return result From 439a999c02c172fe1d0299bc3a7f779d719e8a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 26 Aug 2018 12:47:15 +0200 Subject: [PATCH 016/721] Add hook support in domain_dns_conf --- src/yunohost/domain.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 560a6fda5..644b233d2 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -188,6 +188,7 @@ def domain_dns_conf(domain, ttl=None): ttl -- Time to live """ + from yunohost.hook import hook_callback ttl = 3600 if ttl is None else ttl @@ -209,6 +210,11 @@ def domain_dns_conf(domain, ttl=None): for record in dns_conf["mail"]: result += "\n{name} {ttl} IN {type} {value}".format(**record) + result += "\n\n" + result += "; Custom\n" + + result += ''.join(hook_callback('custom_dns_rules', args=[])['stdreturn']) + is_cli = True if msettings.get('interface') == 'cli' else False if is_cli: logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation")) From 5a27e314ff78fbc29b8c9849a555732977e9b223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 26 Aug 2018 14:59:52 +0200 Subject: [PATCH 017/721] Get custom dns conf in _build_dns_conf --- src/yunohost/domain.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 644b233d2..475b23223 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -38,6 +38,7 @@ import yunohost.certificate from yunohost.service import service_regen_conf from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation +from yunohost.hook import hook_callback logger = getActionLogger('yunohost.domain') @@ -188,7 +189,6 @@ def domain_dns_conf(domain, ttl=None): ttl -- Time to live """ - from yunohost.hook import hook_callback ttl = 3600 if ttl is None else ttl @@ -212,8 +212,8 @@ def domain_dns_conf(domain, ttl=None): result += "\n\n" result += "; Custom\n" - - result += ''.join(hook_callback('custom_dns_rules', args=[])['stdreturn']) + for record in dns_conf["custom"]: + result += "\n{name} {ttl} IN {type} {value}".format(**record) is_cli = True if msettings.get('interface') == 'cli' else False if is_cli: @@ -393,12 +393,22 @@ def _build_dns_conf(domain, ttl=3600): ["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'], ] - return { + # Custom + hookres = hook_callback('custom_dns_rules', args=[domain]) + print(hookres) + custom = [] + for h in hookres.values() : + custom.extend(h['stdreturn']) + + res = { "basic": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in basic], "xmpp": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in xmpp], "mail": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in mail], + "custom": custom, } + return res + def _get_DKIM(domain): DKIM_file = '/etc/dkim/{domain}.mail.txt'.format(domain=domain) From f83e9cae7d24ec8e3ca6c80ce71e7e5503444093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 29 Aug 2018 21:49:35 +0200 Subject: [PATCH 018/721] Dns hook : add specific comment for each hook --- src/yunohost/domain.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 475b23223..6c094e80b 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -210,10 +210,12 @@ def domain_dns_conf(domain, ttl=None): for record in dns_conf["mail"]: result += "\n{name} {ttl} IN {type} {value}".format(**record) - result += "\n\n" - result += "; Custom\n" - for record in dns_conf["custom"]: - result += "\n{name} {ttl} IN {type} {value}".format(**record) + for name, record_list in dns_conf.items(): + if name not in ("basic", "xmpp", "mail"): + result += "\n\n" + result += "; " + name + "\n" + for record in record_list: + result += "\n{name} {ttl} IN {type} {value}".format(**record) is_cli = True if msettings.get('interface') == 'cli' else False if is_cli: @@ -393,20 +395,18 @@ def _build_dns_conf(domain, ttl=3600): ["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'], ] - # Custom - hookres = hook_callback('custom_dns_rules', args=[domain]) - print(hookres) - custom = [] - for h in hookres.values() : - custom.extend(h['stdreturn']) - + # Official record res = { "basic": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in basic], "xmpp": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in xmpp], "mail": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in mail], - "custom": custom, } + # Custom record + hookres = hook_callback('custom_dns_rules', args=[domain]) + for n,v in hookres.items() : + res[n] = v['stdreturn'] + return res From 8ec6f2b81a2ee91e450a3a49d7fa627713d61020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 30 Aug 2018 17:37:28 +0200 Subject: [PATCH 019/721] Fix error if hook return nothing --- src/yunohost/hook.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index ce83ce011..7ad95c8ab 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -398,10 +398,15 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, errno.EIO, m18n.n('hook_exec_failed', path=path)) try: - returnjson = read_json(stdreturn) + with open(stdreturn, 'r') as f: + if f.read() != '': + returnjson = read_json(stdreturn) + else: + returnjson = {} except Exception as e: returnjson = {} - errno.EIO, m18n.n('hook_json_return_error', path=path, msg=str(e)) + raise MoulinetteError( + errno.EIO, m18n.n('hook_json_return_error', path=path, msg=str(e))) stdreturndir = os.path.split(stdreturn)[0] os.remove(stdreturn) From a20b72c2dd26db2721a6d9d9b61fcc3ea116abb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 4 Sep 2018 22:50:45 +0200 Subject: [PATCH 020/721] Add comment about DNS datastructure returned --- src/yunohost/domain.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 6c094e80b..a9df20026 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -342,6 +342,9 @@ def _build_dns_conf(domain, ttl=3600): {"type": "TXT", "name": "mail._domainkey", "value": "\"v=DKIM1; k=rsa; p=some-super-long-key\"", "ttl": 3600}, {"type": "TXT", "name": "_dmarc", "value": "\"v=DMARC1; p=none\"", "ttl": 3600} ], + "example_of_a_custom_rule": [ + {"type": "SRV", "name": "_matrix", "value": "domain.tld.", "ttl": 3600} + ], } """ From 16e00fb7e6bb011f7f64f97f70412c848b97e814 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Wed, 10 Oct 2018 21:15:06 +0200 Subject: [PATCH 021/721] Use getopts for ynh_use_logrotate --- data/helpers.d/backend | 50 ++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 94e26350c..d67d36333 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -1,9 +1,11 @@ +#!/bin/bash + # Use logrotate to manage the logfile # -# usage: ynh_use_logrotate [logfile] [--non-append|--append] [specific_user/specific_group] -# | arg: logfile - absolute path of logfile -# | arg: --non-append - (Option) Replace the config file instead of appending this new config. -# | arg: specific_user : run logrotate as the specified user and group. If not specified logrotate is runned as root. +# usage: ynh_use_logrotate [--logfile=/log/file] [--nonappend] [--specific_user=user/group] +# | arg: -l, --logfile= - absolute path of logfile +# | arg: -n, --nonappend - (Option) Replace the config file instead of appending this new config. +# | arg: -u, --specific_user : run logrotate as the specified user and group. If not specified logrotate is runned as root. # # If no argument provided, a standard directory will be use. /var/log/${app} # You can provide a path with the directory only or with the logfile. @@ -13,28 +15,52 @@ # It's possible to use this helper several times, each config will be added to the same logrotate config file. # Unless you use the option --non-append ynh_use_logrotate () { - local customtee="tee -a" - local user_group="${3:-}" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [l]=logfile= [n]=nonappend [u]=specific_user= [y]=non [a]=append ) + # [y]=non [a]=append are only for legacy purpose, to not fail on the old option '--non-append' + local logfile + local nonappend + local specific_user + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local logfile="${logfile:-}" + local nonappend="${nonappend:-0}" + local specific_user="${specific_user:-}" + + # LEGACY CODE - PRE GETOPTS if [ $# -gt 0 ] && [ "$1" == "--non-append" ]; then - customtee="tee" + nonappend=1 # Destroy this argument for the next command. shift elif [ $# -gt 1 ] && [ "$2" == "--non-append" ]; then - customtee="tee" + nonappend=1 fi - if [ $# -gt 0 ]; then + + if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]; then if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile local logfile=$1 # In this case, focus logrotate on the logfile else local logfile=$1/*.log # Else, uses the directory and all logfile into it. fi + fi + # LEGACY CODE + + local customtee="tee -a" + if [ "$nonappend" -eq 1 ]; then + customtee="tee" + fi + if [ -n "$logfile" ] + then + if [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile + local logfile="$1/*.log" # Else, uses the directory and all logfile into it. + fi else - local logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log + logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log fi local su_directive="" - if [[ -n $user_group ]]; then + if [[ -n $specific_user ]]; then su_directive=" # Run logorotate as specific user - group - su ${user_group%/*} ${user_group#*/}" + su ${specific_user%/*} ${specific_user#*/}" fi cat > ./${app}-logrotate << EOF # Build a config file for logrotate From f354c4b8332fc6d0d6ed4d293d37c8175570292c Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 16 Oct 2018 16:57:24 +0200 Subject: [PATCH 022/721] Use getopts for ynh_add_systemd_config and ynh_remove_systemd_config --- data/helpers.d/backend | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index d67d36333..5103998a2 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -99,9 +99,9 @@ ynh_remove_logrotate () { # Create a dedicated systemd config # -# usage: ynh_add_systemd_config [service] [template] -# | arg: service - Service name (optionnal, $app by default) -# | arg: template - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) +# usage: ynh_add_systemd_config [--service=service] [--template=template] +# | arg: -s, --service - Service name (optionnal, $app by default) +# | arg: -t, --template - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template) # # This will use the template ../conf/.service # to generate a systemd config, by replacing the following keywords @@ -112,11 +112,18 @@ ynh_remove_logrotate () { # __FINALPATH__ by $final_path # ynh_add_systemd_config () { - local service_name="${1:-$app}" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [s]=service= [t]=template ) + local service + local template + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local service="${service:-$app}" + local template="${nonappend:-systemd.service}" - finalsystemdconf="/etc/systemd/system/$service_name.service" + finalsystemdconf="/etc/systemd/system/$service.service" ynh_backup_if_checksum_is_different "$finalsystemdconf" - sudo cp ../conf/${2:-systemd.service} "$finalsystemdconf" + sudo cp ../conf/$template "$finalsystemdconf" # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. # Substitute in a nginx config file only if the variable is not empty @@ -129,22 +136,27 @@ ynh_add_systemd_config () { ynh_store_file_checksum "$finalsystemdconf" sudo chown root: "$finalsystemdconf" - sudo systemctl enable $service_name + sudo systemctl enable $service sudo systemctl daemon-reload } # Remove the dedicated systemd config # -# usage: ynh_remove_systemd_config [service] -# | arg: service - Service name (optionnal, $app by default) +# usage: ynh_remove_systemd_config [--service=service] +# | arg: -s, --service - Service name (optionnal, $app by default) # ynh_remove_systemd_config () { - local service_name="${1:-$app}" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [s]=service= ) + local service + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local service="${service:-$app}" - local finalsystemdconf="/etc/systemd/system/$service_name.service" + local finalsystemdconf="/etc/systemd/system/$service.service" if [ -e "$finalsystemdconf" ]; then - sudo systemctl stop $service_name - sudo systemctl disable $service_name + sudo systemctl stop $service + sudo systemctl disable $service ynh_secure_remove "$finalsystemdconf" sudo systemctl daemon-reload fi From d59401f08e7ee5a0007f6060a077d37464de0c5d Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 16 Oct 2018 23:41:11 +0200 Subject: [PATCH 023/721] Use getopts for helpers in filesystem --- data/helpers.d/filesystem | 176 ++++++++++++++++++++++---------------- 1 file changed, 103 insertions(+), 73 deletions(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index c07de2ece..c9c58e2fe 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -10,13 +10,12 @@ CAN_BIND=${CAN_BIND:-1} # # If DEST is ended by a slash it complete this path with the basename of SRC. # -# usage: ynh_backup src [dest [is_big [arg]]] -# | arg: src - file or directory to bind or symlink or copy. it shouldn't be in +# usage: ynh_backup --src_path=src_path [--dest_path=dest_path] [--is_big] +# | arg: -s, --src_path - file or directory to bind or symlink or copy. it shouldn't be in # the backup dir. -# | arg: dest - destination file or directory inside the +# | arg: -d, --dest_path - destination file or directory inside the # backup dir -# | arg: is_big - 1 to indicate data are big (mail, video, image ...) -# | arg: arg - Deprecated arg +# | arg: -b, --is_big - Indicate data are big (mail, video, image ...) # # example: # # Wordpress app context @@ -43,15 +42,23 @@ CAN_BIND=${CAN_BIND:-1} # ynh_backup() { # TODO find a way to avoid injection by file strange naming ! - local SRC_PATH="$1" - local DEST_PATH="${2:-}" - local IS_BIG="${3:-0}" + + # Declare an array to define the options of this helper. + declare -Ar args_array=( [s]=src_path= [d]=dest_path [b]=is_big ) + local src_path + local dest_path + local is_big + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local dest_path="${dest_path:-}" + local is_big="${is_big:-0}" + BACKUP_CORE_ONLY=${BACKUP_CORE_ONLY:-0} # If backing up core only (used by ynh_backup_before_upgrade), # don't backup big data items - if [ "$IS_BIG" == "1" ] && [ "$BACKUP_CORE_ONLY" == "1" ] ; then - echo "$SRC_PATH will not be saved, because backup_core_only is set." >&2 + if [ "$is_big" == "1" ] && [ "$BACKUP_CORE_ONLY" == "1" ] ; then + echo "$src_path will not be saved, because backup_core_only is set." >&2 return 0 fi @@ -59,24 +66,24 @@ ynh_backup() { # Format correctly source and destination paths # ============================================================================== # Be sure the source path is not empty - [[ -e "${SRC_PATH}" ]] || { - echo "Source path '${SRC_PATH}' does not exist" >&2 + [[ -e "${src_path}" ]] || { + echo "Source path '${src_path}' does not exist" >&2 return 1 } # Transform the source path as an absolute path # If it's a dir remove the ending / - SRC_PATH=$(realpath "$SRC_PATH") + src_path=$(realpath "$src_path") # If there is no destination path, initialize it with the source path # relative to "/". - # eg: SRC_PATH=/etc/yunohost -> DEST_PATH=etc/yunohost - if [[ -z "$DEST_PATH" ]]; then + # eg: src_path=/etc/yunohost -> dest_path=etc/yunohost + if [[ -z "$dest_path" ]]; then - DEST_PATH="${SRC_PATH#/}" + dest_path="${src_path#/}" else - if [[ "${DEST_PATH:0:1}" == "/" ]]; then + if [[ "${dest_path:0:1}" == "/" ]]; then # If the destination path is an absolute path, transform it as a path # relative to the current working directory ($YNH_CWD) @@ -85,43 +92,43 @@ ynh_backup() { # $YNH_BACKUP_DIR/apps/APP_INSTANCE_NAME/backup/ # # If it's a system part backup script, YNH_CWD is equal to $YNH_BACKUP_DIR - DEST_PATH="${DEST_PATH#$YNH_CWD/}" + dest_path="${dest_path#$YNH_CWD/}" # Case where $2 is an absolute dir but doesn't begin with $YNH_CWD - [[ "${DEST_PATH:0:1}" == "/" ]] \ - && DEST_PATH="${DEST_PATH#/}" + [[ "${dest_path:0:1}" == "/" ]] \ + && dest_path="${dest_path#/}" fi - # Complete DEST_PATH if ended by a / - [[ "${DEST_PATH: -1}" == "/" ]] \ - && DEST_PATH="${DEST_PATH}/$(basename $SRC_PATH)" + # Complete dest_path if ended by a / + [[ "${dest_path: -1}" == "/" ]] \ + && dest_path="${dest_path}/$(basename $src_path)" fi - # Check if DEST_PATH already exists in tmp archive - [[ ! -e "${DEST_PATH}" ]] || { - echo "Destination path '${DEST_PATH}' already exist" >&2 + # Check if dest_path already exists in tmp archive + [[ ! -e "${dest_path}" ]] || { + echo "Destination path '${dest_path}' already exist" >&2 return 1 } # Add the relative current working directory to the destination path - local REL_DIR="${YNH_CWD#$YNH_BACKUP_DIR}" - REL_DIR="${REL_DIR%/}/" - DEST_PATH="${REL_DIR}${DEST_PATH}" - DEST_PATH="${DEST_PATH#/}" + local rel_dir="${YNH_CWD#$YNH_BACKUP_DIR}" + rel_dir="${rel_dir%/}/" + dest_path="${rel_dir}${dest_path}" + dest_path="${dest_path#/}" # ============================================================================== # ============================================================================== # Write file to backup into backup_list # ============================================================================== - local SRC=$(echo "${SRC_PATH}" | sed -r 's/"/\"\"/g') - local DEST=$(echo "${DEST_PATH}" | sed -r 's/"/\"\"/g') - echo "\"${SRC}\",\"${DEST}\"" >> "${YNH_BACKUP_CSV}" + local src=$(echo "${src_path}" | sed -r 's/"/\"\"/g') + local dest=$(echo "${dest_path}" | sed -r 's/"/\"\"/g') + echo "\"${src}\",\"${dest}\"" >> "${YNH_BACKUP_CSV}" # ============================================================================== # Create the parent dir of the destination path # It's for retro compatibility, some script consider ynh_backup creates this dir - mkdir -p $(dirname "$YNH_BACKUP_DIR/${DEST_PATH}") + mkdir -p $(dirname "$YNH_BACKUP_DIR/${dest_path}") } # Restore all files linked to the restore hook or to the restore app script @@ -168,10 +175,10 @@ with open(sys.argv[1], 'r') as backup_file: # Use the registered path in backup_list by ynh_backup to restore the file at # the good place. # -# usage: ynh_restore_file ORIGIN_PATH [ DEST_PATH ] -# | arg: ORIGIN_PATH - Path where was located the file or the directory before +# usage: ynh_restore_file --origin_path=origin_path [--dest_path=dest_path] +# | arg: -o, --origin_path - Path where was located the file or the directory before # to be backuped or relative path to $YNH_CWD where it is located in the backup archive -# | arg: DEST_PATH - Path where restore the file or the dir, if unspecified, +# | arg: -d, --dest_path - Path where restore the file or the dir, if unspecified, # the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in # the archive, the destination will be searched into backup.csv # @@ -189,43 +196,50 @@ with open(sys.argv[1], 'r') as backup_file: # ynh_restore_file "conf/nginx.conf" # ynh_restore_file () { - local ORIGIN_PATH="/${1#/}" - local ARCHIVE_PATH="$YNH_CWD${ORIGIN_PATH}" - # Default value for DEST_PATH = /$ORIGIN_PATH - local DEST_PATH="${2:-$ORIGIN_PATH}" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [o]=origin_path= [d]=dest_path= ) + local origin_path + local archive_path + local dest_path + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local origin_path="/${origin_path#/}" + local archive_path="$YNH_CWD${origin_path}" + # Default value for dest_path = /$origin_path + local dest_path="${dest_path:-$origin_path}" - # If ARCHIVE_PATH doesn't exist, search for a corresponding path in CSV - if [ ! -d "$ARCHIVE_PATH" ] && [ ! -f "$ARCHIVE_PATH" ] && [ ! -L "$ARCHIVE_PATH" ]; then - ARCHIVE_PATH="$YNH_BACKUP_DIR/$(_get_archive_path \"$ORIGIN_PATH\")" + # If archive_path doesn't exist, search for a corresponding path in CSV + if [ ! -d "$archive_path" ] && [ ! -f "$archive_path" ] && [ ! -L "$archive_path" ]; then + archive_path="$YNH_BACKUP_DIR/$(_get_archive_path \"$origin_path\")" fi # Move the old directory if it already exists - if [[ -e "${DEST_PATH}" ]] + if [[ -e "${dest_path}" ]] then # Check if the file/dir size is less than 500 Mo - if [[ $(du -sb ${DEST_PATH} | cut -d"/" -f1) -le "500000000" ]] + if [[ $(du -sb ${dest_path} | cut -d"/" -f1) -le "500000000" ]] then - local backup_file="/home/yunohost.conf/backup/${DEST_PATH}.backup.$(date '+%Y%m%d.%H%M%S')" + local backup_file="/home/yunohost.conf/backup/${dest_path}.backup.$(date '+%Y%m%d.%H%M%S')" mkdir -p "$(dirname "$backup_file")" - mv "${DEST_PATH}" "$backup_file" # Move the current file or directory + mv "${dest_path}" "$backup_file" # Move the current file or directory else - ynh_secure_remove ${DEST_PATH} + ynh_secure_remove ${dest_path} fi fi - # Restore ORIGIN_PATH into DEST_PATH - mkdir -p $(dirname "$DEST_PATH") + # Restore origin_path into dest_path + mkdir -p $(dirname "$dest_path") # Do a copy if it's just a mounting point if mountpoint -q $YNH_BACKUP_DIR; then - if [[ -d "${ARCHIVE_PATH}" ]]; then - ARCHIVE_PATH="${ARCHIVE_PATH}/." - mkdir -p "$DEST_PATH" + if [[ -d "${archive_path}" ]]; then + archive_path="${archive_path}/." + mkdir -p "$dest_path" fi - cp -a "$ARCHIVE_PATH" "${DEST_PATH}" + cp -a "$archive_path" "${dest_path}" # Do a move if YNH_BACKUP_DIR is already a copy else - mv "$ARCHIVE_PATH" "${DEST_PATH}" + mv "$archive_path" "${dest_path}" fi } @@ -265,11 +279,17 @@ properly with chmod/chown." >&2 # # $app should be defined when calling this helper # -# usage: ynh_store_file_checksum file -# | arg: file - The file on which the checksum will performed, then stored. +# usage: ynh_store_file_checksum --file=file +# | arg: -f, --file - The file on which the checksum will performed, then stored. ynh_store_file_checksum () { - local checksum_setting_name=checksum_${1//[\/ ]/_} # Replace all '/' and ' ' by '_' - ynh_app_setting_set $app $checksum_setting_name $(sudo md5sum "$1" | cut -d' ' -f1) + # Declare an array to define the options of this helper. + declare -Ar args_array=( [f]=file= ) + local file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' + ynh_app_setting_set $app $checksum_setting_name $(sudo md5sum "$file" | cut -d' ' -f1) } # Verify the checksum and backup the file if it's different @@ -278,12 +298,17 @@ ynh_store_file_checksum () { # # $app should be defined when calling this helper # -# usage: ynh_backup_if_checksum_is_different file -# | arg: file - The file on which the checksum test will be perfomed. +# usage: ynh_backup_if_checksum_is_different --file=file +# | arg: -f, --file - The file on which the checksum test will be perfomed. # # | ret: Return the name a the backup file, or nothing ynh_backup_if_checksum_is_different () { - local file=$1 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [f]=file= ) + local file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' local checksum_value=$(ynh_app_setting_get $app $checksum_setting_name) if [ -n "$checksum_value" ] @@ -318,28 +343,33 @@ ynh_delete_file_checksum () { # Remove a file or a directory securely # -# usage: ynh_secure_remove path_to_remove -# | arg: path_to_remove - File or directory to remove +# usage: ynh_secure_remove file=path_to_remove +# | arg: -f, --file - File or directory to remove ynh_secure_remove () { - local path_to_remove=$1 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [f]=file= ) + local file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local forbidden_path=" \ /var/www \ /home/yunohost.app" - if [[ "$forbidden_path" =~ "$path_to_remove" \ + if [[ "$forbidden_path" =~ "$file" \ # Match all paths or subpaths in $forbidden_path - || "$path_to_remove" =~ ^/[[:alnum:]]+$ \ + || "$file" =~ ^/[[:alnum:]]+$ \ # Match all first level paths from / (Like /var, /root, etc...) - || "${path_to_remove:${#path_to_remove}-1}" = "/" ]] + || "${file:${#file}-1}" = "/" ]] # Match if the path finishes by /. Because it seems there is an empty variable then - echo "Avoid deleting $path_to_remove." >&2 + echo "Avoid deleting $file." >&2 else - if [ -e "$path_to_remove" ] + if [ -e "$file" ] then - sudo rm -R "$path_to_remove" + sudo rm -R "$file" else - echo "$path_to_remove wasn't deleted because it doesn't exist." >&2 + echo "$file wasn't deleted because it doesn't exist." >&2 fi fi } From 4c82dad2bb48a550687c48618b52b8a7e70323f8 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 16 Oct 2018 23:46:54 +0200 Subject: [PATCH 024/721] Use getopts for ip's helpers --- data/helpers.d/ip | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/data/helpers.d/ip b/data/helpers.d/ip index 092cdff4b..09c974782 100644 --- a/data/helpers.d/ip +++ b/data/helpers.d/ip @@ -1,6 +1,6 @@ # Validate an IP address # -# usage: ynh_validate_ip [family] [ip_address] +# usage: ynh_validate_ip --family=family --ip_address=ip_address # | ret: 0 for valid ip addresses, 1 otherwise # # example: ynh_validate_ip 4 111.222.333.444 @@ -9,17 +9,21 @@ ynh_validate_ip() { # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298 - local IP_ADDRESS_FAMILY=$1 - local IP_ADDRESS=$2 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [f]=family= [i]=ip_address= ) + local family + local ip_address + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - [ "$IP_ADDRESS_FAMILY" == "4" ] || [ "$IP_ADDRESS_FAMILY" == "6" ] || return 1 + [ "$family" == "4" ] || [ "$family" == "6" ] || return 1 python /dev/stdin << EOF import socket import sys family = { "4" : socket.AF_INET, "6" : socket.AF_INET6 } try: - socket.inet_pton(family["$IP_ADDRESS_FAMILY"], "$IP_ADDRESS") + socket.inet_pton(family["$family"], "$ip_address") except socket.error: sys.exit(1) sys.exit(0) @@ -30,12 +34,18 @@ EOF # # example: ynh_validate_ip4 111.222.333.444 # -# usage: ynh_validate_ip4 +# usage: ynh_validate_ip4 --ip_address=ip_address # | ret: 0 for valid ipv4 addresses, 1 otherwise # ynh_validate_ip4() { - ynh_validate_ip 4 $1 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [i]=ip_address= ) + local ip_address + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + ynh_validate_ip 4 $ip_address } @@ -43,10 +53,16 @@ ynh_validate_ip4() # # example: ynh_validate_ip6 2000:dead:beef::1 # -# usage: ynh_validate_ip6 +# usage: ynh_validate_ip6 --ip_address=ip_address # | ret: 0 for valid ipv6 addresses, 1 otherwise # ynh_validate_ip6() { - ynh_validate_ip 6 $1 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [i]=ip_address= ) + local ip_address + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + ynh_validate_ip 6 $ip_address } From 35b483a7c083a1aa86865b21a0bbfeedef6c9b28 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Wed, 17 Oct 2018 00:05:42 +0200 Subject: [PATCH 025/721] Use getopts for sql's helpers --- data/helpers.d/mysql | 130 ++++++++++++++++++++++++++++++------------- 1 file changed, 92 insertions(+), 38 deletions(-) diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 7bc93fad5..0cfe65380 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -5,32 +5,57 @@ MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql # example: ynh_mysql_connect_as 'user' 'pass' <<< "UPDATE ...;" # example: ynh_mysql_connect_as 'user' 'pass' < /path/to/file.sql # -# usage: ynh_mysql_connect_as user pwd [db] -# | arg: user - the user name to connect as -# | arg: pwd - the user password -# | arg: db - the database to connect to +# usage: ynh_mysql_connect_as --user=user --password=password [--database=database] +# | arg: -u, --user - the user name to connect as +# | arg: -p, --password - the user password +# | arg: -d, --database - the database to connect to ynh_mysql_connect_as() { - mysql -u "$1" --password="$2" -B "${3:-}" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=user= [p]=password= [d]=database= ) + local user + local password + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" + + mysql -u "$user" --password="$password" -B "$database" } # Execute a command as root user # -# usage: ynh_mysql_execute_as_root sql [db] -# | arg: sql - the SQL command to execute -# | arg: db - the database to connect to +# usage: ynh_mysql_execute_as_root --sql=sql [--database=database] +# | arg: -s, --sql - the SQL command to execute +# | arg: -d, --database - the database to connect to ynh_mysql_execute_as_root() { + # Declare an array to define the options of this helper. + declare -Ar args_array=( [s]=sql= [d]=database= ) + local sql + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" + ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ - "${2:-}" <<< "$1" + "$database" <<< "$sql" } # Execute a command from a file as root user # -# usage: ynh_mysql_execute_file_as_root file [db] -# | arg: file - the file containing SQL commands -# | arg: db - the database to connect to +# usage: ynh_mysql_execute_file_as_root --file=file [--database=database] +# | arg: -f, --file - the file containing SQL commands +# | arg: -d, --database - the database to connect to ynh_mysql_execute_file_as_root() { + # Declare an array to define the options of this helper. + declare -Ar args_array=( [f]=file= [d]=database= ) + local file + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" + ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ - "${2:-}" < "$1" + "$database" < "$file" } # Create a database and grant optionnaly privilegies to a user @@ -73,11 +98,17 @@ ynh_mysql_drop_db() { # # example: ynh_mysql_dump_db 'roundcube' > ./dump.sql # -# usage: ynh_mysql_dump_db db -# | arg: db - the database name to dump +# usage: ynh_mysql_dump_db --database=database +# | arg: -d, --database - the database name to dump # | ret: the mysqldump output ynh_mysql_dump_db() { - mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" --single-transaction --skip-dump-date "$1" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [d]=database= ) + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" --single-transaction --skip-dump-date "$database" } # Create a user @@ -94,11 +125,16 @@ ynh_mysql_create_user() { # Check if a mysql user exists # -# usage: ynh_mysql_user_exists user -# | arg: user - the user for which to check existence +# usage: ynh_mysql_user_exists --user=user +# | arg: -u, --user - the user for which to check existence ynh_mysql_user_exists() { - local user=$1 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=user= ) + local user + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + if [[ -z $(ynh_mysql_execute_as_root "SELECT User from mysql.user WHERE User = '$user';") ]] then return 1 @@ -122,28 +158,40 @@ ynh_mysql_drop_user() { # After executing this helper, the password of the created database will be available in $db_pwd # It will also be stored as "mysqlpwd" into the app settings. # -# usage: ynh_mysql_setup_db user name [pwd] -# | arg: user - Owner of the database -# | arg: name - Name of the database -# | arg: pwd - Password of the database. If not given, a password will be generated +# usage: ynh_mysql_setup_db --db_user=user --db_name=name [--db_password=pwd] +# | arg: -u, --db_user - Owner of the database +# | arg: -n, --db_name - Name of the database +# | arg: -p, --db_password - Password of the database. If not given, a password will be generated ynh_mysql_setup_db () { - local db_user="$1" - local db_name="$2" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=db_user= [n]=db_name= [p]=db_password= ) + local db_user + local db_name + local db_password + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local new_db_pwd=$(ynh_string_random) # Generate a random password - # If $3 is not given, use new_db_pwd instead for db_pwd. - db_pwd="${3:-$new_db_pwd}" - ynh_mysql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database - ynh_app_setting_set $app mysqlpwd $db_pwd # Store the password in the app's config + # If $db_password is not given, use new_db_pwd instead for db_password. + db_password="${db_password:-$new_db_pwd}" + + ynh_mysql_create_db "$db_name" "$db_user" "$db_password" # Create the database + ynh_app_setting_set $app mysqlpwd $db_password # Store the password in the app's config } # Remove a database if it exists, and the associated user # -# usage: ynh_mysql_remove_db user name -# | arg: user - Owner of the database -# | arg: name - Name of the database +# usage: ynh_mysql_remove_db --db_user=user --db_name=name +# | arg: -u, --db_user - Owner of the database +# | arg: -n, --db_name - Name of the database ynh_mysql_remove_db () { - local db_user="$1" - local db_name="$2" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=db_user= [n]=db_name= ) + local db_user + local db_name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local mysql_root_password=$(sudo cat $MYSQL_ROOT_PWD_FILE) if mysqlshow -u root -p$mysql_root_password | grep -q "^| $db_name"; then # Check if the database exists echo "Removing database $db_name" >&2 @@ -163,10 +211,16 @@ ynh_mysql_remove_db () { # # example: dbname=$(ynh_sanitize_dbid $app) # -# usage: ynh_sanitize_dbid name -# | arg: name - name to correct/sanitize +# usage: ynh_sanitize_dbid --db_name=name +# | arg: -n, --db_name - name to correct/sanitize # | ret: the corrected name ynh_sanitize_dbid () { - local dbid=${1//[-.]/_} # We should avoid having - and . in the name of databases. They are replaced by _ - echo $dbid + # Declare an array to define the options of this helper. + declare -Ar args_array=( [n]=db_name= ) + local db_name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + # We should avoid having - and . in the name of databases. They are replaced by _ + echo ${db_name//[-.]/_} } From 743bc0d436b893375f08e62102c5ddc5be0fa069 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Wed, 17 Oct 2018 00:11:03 +0200 Subject: [PATCH 026/721] Fix bad copy paste --- data/helpers.d/filesystem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index c9c58e2fe..34b0487cc 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -44,7 +44,7 @@ ynh_backup() { # TODO find a way to avoid injection by file strange naming ! # Declare an array to define the options of this helper. - declare -Ar args_array=( [s]=src_path= [d]=dest_path [b]=is_big ) + declare -Ar args_array=( [s]=src_path= [d]=dest_path= [b]=is_big ) local src_path local dest_path local is_big From 2840531cf44b25c042af7e3dc6c007cae31105b0 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Wed, 17 Oct 2018 00:11:08 +0200 Subject: [PATCH 027/721] Fix bad copy paste --- data/helpers.d/backend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 5103998a2..025ce45f0 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -113,7 +113,7 @@ ynh_remove_logrotate () { # ynh_add_systemd_config () { # Declare an array to define the options of this helper. - declare -Ar args_array=( [s]=service= [t]=template ) + declare -Ar args_array=( [s]=service= [t]=template= ) local service local template # Manage arguments with getopts From 8012f12797e7025f6832db6fa60e60db3c8c247a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 5 Dec 2018 00:32:42 +0100 Subject: [PATCH 028/721] Fix raise error --- src/yunohost/hook.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 121630e9f..4e219d226 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -404,7 +404,8 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, else: returnjson = {} except Exception as e: - returnjson = {} + os.remove(stdreturn) + os.rmdir(stdreturndir) raise MoulinetteError( errno.EIO, m18n.n('hook_json_return_error', path=path, msg=str(e))) From a701b425199fd6547919b0882288cde36f2d71ba Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 21 Dec 2018 20:40:02 +0100 Subject: [PATCH 029/721] Use getopts for network's helpers --- data/helpers.d/network | 66 +++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index f9e37e6cc..fc7eb3f69 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -8,10 +8,15 @@ # ynh_normalize_url_path /example/ -> /example # ynh_normalize_url_path / -> / # -# usage: ynh_normalize_url_path path_to_normalize -# | arg: url_path_to_normalize - URL path to normalize before using it +# usage: ynh_normalize_url_path --path_url=path_to_normalize +# | arg: -p, --path_url - URL path to normalize before using it ynh_normalize_url_path () { - local path_url=$1 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [p]=path_url= ) + local path_url + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + test -n "$path_url" || ynh_die "ynh_normalize_url_path expect a URL path as first argument and received nothing." if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a / path_url="/$path_url" # Add / at begin of path variable @@ -24,12 +29,17 @@ ynh_normalize_url_path () { # Find a free port and return it # -# example: port=$(ynh_find_port 8080) +# example: port=$(ynh_find_port --port=8080) # -# usage: ynh_find_port begin_port -# | arg: begin_port - port to start to search +# usage: ynh_find_port --port=begin_port +# | arg: -p, --port - port to start to search ynh_find_port () { - local port=$1 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [p]=port= ) + local port + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + test -n "$port" || ynh_die "The argument of ynh_find_port must be a valid port." while netcat -z 127.0.0.1 $port # Check if the port is free do @@ -40,28 +50,38 @@ ynh_find_port () { # Check availability of a web path # -# example: ynh_webpath_available some.domain.tld /coffee +# example: ynh_webpath_available --domain=some.domain.tld --path_url=/coffee # -# usage: ynh_webpath_available domain path -# | arg: domain - the domain/host of the url -# | arg: path - the web path to check the availability of +# usage: ynh_webpath_available --domain=domain --path_url=path +# | arg: -d, --domain - the domain/host of the url +# | arg: -p, --path_url - the web path to check the availability of ynh_webpath_available () { - local domain=$1 - local path=$2 - sudo yunohost domain url-available $domain $path + # Declare an array to define the options of this helper. + declare -Ar args_array=( [d]=domain= [p]=path_url= ) + local domain + local path_url + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + sudo yunohost domain url-available $domain $path_url } # Register/book a web path for an app # -# example: ynh_webpath_register wordpress some.domain.tld /coffee +# example: ynh_webpath_register --app=wordpress --domain=some.domain.tld --path_url=/coffee # -# usage: ynh_webpath_register app domain path -# | arg: app - the app for which the domain should be registered -# | arg: domain - the domain/host of the web path -# | arg: path - the web path to be registered +# usage: ynh_webpath_register --app=app --domain=domain --path_url=path +# | arg: -a, --app - the app for which the domain should be registered +# | arg: -d, --domain - the domain/host of the web path +# | arg: -p, --path_url - the web path to be registered ynh_webpath_register () { - local app=$1 - local domain=$2 - local path=$3 - sudo yunohost app register-url $app $domain $path + # Declare an array to define the options of this helper. + declare -Ar args_array=( [a]=app= [d]=domain= [p]=path_url= ) + local app + local domain + local path_url + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + sudo yunohost app register-url $app $domain $path_url } From 0aed8a692287413af1f38e8adefd684c78a3bccf Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 21 Dec 2018 20:43:57 +0100 Subject: [PATCH 030/721] Use getopts for nodejs helper --- data/helpers.d/nodejs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 5111fa671..d56ef8272 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -53,13 +53,18 @@ ynh_use_nodejs () { # # ynh_install_nodejs will install the version of node provided as argument by using n. # -# usage: ynh_install_nodejs [nodejs_version] -# | arg: nodejs_version - Version of node to install. +# usage: ynh_install_nodejs --nodejs_version=nodejs_version +# | arg: -n, --nodejs_version - Version of node to install. # If possible, prefer to use major version number (e.g. 8 instead of 8.10.0). # The crontab will handle the update of minor versions when needed. ynh_install_nodejs () { # Use n, https://github.com/tj/n to manage the nodejs versions - nodejs_version="$1" + + # Declare an array to define the options of this helper. + declare -Ar args_array=( [n]=nodejs_version= ) + local nodejs_version + # Manage arguments with getopts + ynh_handle_getopts_args "$@" # Create $n_install_dir mkdir -p "$n_install_dir" From 0654c68af4c0a14f015d867353960e9f1e7bd4f0 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 21 Dec 2018 20:50:57 +0100 Subject: [PATCH 031/721] Use getopts for package's helpers --- data/helpers.d/package | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/data/helpers.d/package b/data/helpers.d/package index db3b50e0e..6a0f9d1c7 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -1,24 +1,36 @@ # Check either a package is installed or not # -# example: ynh_package_is_installed 'yunohost' && echo "ok" +# example: ynh_package_is_installed --package=yunohost && echo "ok" # -# usage: ynh_package_is_installed name -# | arg: name - the package name to check +# usage: ynh_package_is_installed --package=name +# | arg: -p, --package - the package name to check ynh_package_is_installed() { - dpkg-query -W -f '${Status}' "$1" 2>/dev/null \ + # Declare an array to define the options of this helper. + declare -Ar args_array=( [p]=package= ) + local package + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + dpkg-query -W -f '${Status}' "$package" 2>/dev/null \ | grep -c "ok installed" &>/dev/null } # Get the version of an installed package # -# example: version=$(ynh_package_version 'yunohost') +# example: version=$(ynh_package_version --package=yunohost) # -# usage: ynh_package_version name -# | arg: name - the package name to get version +# usage: ynh_package_version --package=name +# | arg: -p, --package - the package name to get version # | ret: the version or an empty string ynh_package_version() { - if ynh_package_is_installed "$1"; then - dpkg-query -W -f '${Version}' "$1" 2>/dev/null + # Declare an array to define the options of this helper. + declare -Ar args_array=( [p]=package= ) + local package + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + if ynh_package_is_installed "$package"; then + dpkg-query -W -f '${Version}' "$package" 2>/dev/null else echo '' fi From fe6a414ebf9ef18cb78ccf081f5085ebe15c0362 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 21 Dec 2018 20:57:15 +0100 Subject: [PATCH 032/721] Use getopts for print's helpers --- data/helpers.d/print | 47 +++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/data/helpers.d/print b/data/helpers.d/print index 93d402e64..7d32207f6 100644 --- a/data/helpers.d/print +++ b/data/helpers.d/print @@ -1,15 +1,28 @@ # Print a message to stderr and exit -# usage: ynh_die MSG [RETCODE] +# usage: ynh_die --message=MSG [--ret_code=RETCODE] ynh_die() { - echo "$1" 1>&2 - exit "${2:-1}" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [m]=message= [c]=ret_code= ) + local message + local ret_code + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + echo "$message" 1>&2 + exit "${ret_code:-1}" } # Display a message in the 'INFO' logging category # -# usage: ynh_info "Some message" +# usage: ynh_info --message="Some message" ynh_print_info() { - echo "$1" >> "$YNH_STDINFO" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [m]=message= ) + local message + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + echo "$message" >> "$YNH_STDINFO" } # Ignore the yunohost-cli log to prevent errors with conditional commands @@ -39,18 +52,30 @@ ynh_print_log () { # Print a warning on stderr # -# usage: ynh_print_warn "Text to print" -# | arg: text - The text to print +# usage: ynh_print_warn --message="Text to print" +# | arg: -m, --message - The text to print ynh_print_warn () { - ynh_print_log "\e[93m\e[1m[WARN]\e[0m ${1}" >&2 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [m]=message= ) + local message + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + ynh_print_log "\e[93m\e[1m[WARN]\e[0m ${message}" >&2 } # Print an error on stderr # -# usage: ynh_print_err "Text to print" -# | arg: text - The text to print +# usage: ynh_print_err --message="Text to print" +# | arg: -m, --message - The text to print ynh_print_err () { - ynh_print_log "\e[91m\e[1m[ERR]\e[0m ${1}" >&2 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [m]=message= ) + local message + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + ynh_print_log "\e[91m\e[1m[ERR]\e[0m ${message}" >&2 } # Execute a command and print the result as an error From f24b7a6fda2e72660d7db6a4841878cf56551129 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 21 Dec 2018 21:01:43 +0100 Subject: [PATCH 033/721] Use getopts for setting's helpers --- data/helpers.d/setting | 48 ++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index ad036ba4f..29eeecd3c 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -1,27 +1,49 @@ # Get an application setting # -# usage: ynh_app_setting_get app key -# | arg: app - the application id -# | arg: key - the setting to get +# usage: ynh_app_setting_get --app=app --key=key +# | arg: -a, --app - the application id +# | arg: -k, --key - the setting to get ynh_app_setting_get() { - sudo yunohost app setting "$1" "$2" --output-as plain --quiet + # Declare an array to define the options of this helper. + declare -Ar args_array=( [a]=app= [k]=key= ) + local app + local key + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + sudo yunohost app setting "$app" "$key" --output-as plain --quiet } # Set an application setting # -# usage: ynh_app_setting_set app key value -# | arg: app - the application id -# | arg: key - the setting name to set -# | arg: value - the setting value to set +# usage: ynh_app_setting_set --app=app --key=key --value=value +# | arg: -a, --app - the application id +# | arg: -k, --key - the setting name to set +# | arg: -v, --value - the setting value to set ynh_app_setting_set() { - sudo yunohost app setting "$1" "$2" --value="$3" --quiet + # Declare an array to define the options of this helper. + declare -Ar args_array=( [a]=app= [k]=key= [v]=value= ) + local app + local key + local value + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + sudo yunohost app setting "$app" "$key" --value="$value" --quiet } # Delete an application setting # -# usage: ynh_app_setting_delete app key -# | arg: app - the application id -# | arg: key - the setting to delete +# usage: ynh_app_setting_delete --app=app --key=key +# | arg: -a, --app - the application id +# | arg: -k, --key - the setting to delete ynh_app_setting_delete() { - sudo yunohost app setting -d "$1" "$2" --quiet + # Declare an array to define the options of this helper. + declare -Ar args_array=( [a]=app= [k]=key= ) + local app + local key + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + sudo yunohost app setting -d "$app" "$key" --quiet } From c057d38fe136833218d06058374f3b11544b8224 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 17:58:22 +0100 Subject: [PATCH 034/721] Use getopts for string's helpers --- data/helpers.d/string | 59 +++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/data/helpers.d/string b/data/helpers.d/string index f708b31b1..c13497f9f 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -1,59 +1,74 @@ # Generate a random string # -# example: pwd=$(ynh_string_random 8) +# example: pwd=$(ynh_string_random --length=8) # -# usage: ynh_string_random [length] -# | arg: length - the string length to generate (default: 24) +# usage: ynh_string_random [--length=string_length] +# | arg: -l, --length - the string length to generate (default: 24) ynh_string_random() { + # Declare an array to define the options of this helper. + declare -Ar args_array=( [l]=length= ) + local length + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + length=${length:-24} + dd if=/dev/urandom bs=1 count=200 2> /dev/null \ | tr -c -d 'A-Za-z0-9' \ - | sed -n 's/\(.\{'"${1:-24}"'\}\).*/\1/p' + | sed -n 's/\(.\{'"$length"'\}\).*/\1/p' } # Substitute/replace a string (or expression) by another in a file # # usage: ynh_replace_string match_string replace_string target_file -# | arg: match_string - String to be searched and replaced in the file -# | arg: replace_string - String that will replace matches -# | arg: target_file - File in which the string will be replaced. +# | arg: -m, --match_string - String to be searched and replaced in the file +# | arg: -r, --replace_string - String that will replace matches +# | arg: -f, --target_file - File in which the string will be replaced. # # As this helper is based on sed command, regular expressions and # references to sub-expressions can be used # (see sed manual page for more information) ynh_replace_string () { + # Declare an array to define the options of this helper. + declare -Ar args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) + local match_string + local replace_string + local target_file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local delimit=@ - local match_string=$1 - local replace_string=$2 - local workfile=$3 - # Escape the delimiter if it's in the string. match_string=${match_string//${delimit}/"\\${delimit}"} replace_string=${replace_string//${delimit}/"\\${delimit}"} - sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$workfile" + sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$target_file" } # Substitute/replace a special string by another in a file # # usage: ynh_replace_special_string match_string replace_string target_file -# | arg: match_string - String to be searched and replaced in the file -# | arg: replace_string - String that will replace matches -# | arg: target_file - File in which the string will be replaced. +# | arg: -m, --match_string - String to be searched and replaced in the file +# | arg: -r, --replace_string - String that will replace matches +# | arg: -t, --target_file - File in which the string will be replaced. # # This helper will use ynh_replace_string, but as you can use special # characters, you can't use some regular expressions and sub-expressions. ynh_replace_special_string () { - local match_string=$1 - local replace_string=$2 - local workfile=$3 + # Declare an array to define the options of this helper. + declare -Ar args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) + local match_string + local replace_string + local target_file + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - # Escape any backslash to preserve them as simple backslash. - match_string=${match_string//\\/"\\\\"} - replace_string=${replace_string//\\/"\\\\"} + # Escape any backslash to preserve them as simple backslash. + match_string=${match_string//\\/"\\\\"} + replace_string=${replace_string//\\/"\\\\"} # Escape the & character, who has a special function in sed. match_string=${match_string//&/"\&"} replace_string=${replace_string//&/"\&"} - ynh_replace_string "$match_string" "$replace_string" "$workfile" + ynh_replace_string "$match_string" "$replace_string" "$target_file" } From 6425117c4dcaa7f498dc91d22ddf13b1c388e64e Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:12:33 +0100 Subject: [PATCH 035/721] Use getopts for user's helpers --- data/helpers.d/user | 76 ++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index 47e6eb88a..4a2e7c55d 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -2,22 +2,35 @@ # # example: ynh_user_exists 'toto' || exit 1 # -# usage: ynh_user_exists username -# | arg: username - the username to check +# usage: ynh_user_exists --username=username +# | arg: -u, --username - the username to check ynh_user_exists() { - sudo yunohost user list --output-as json | grep -q "\"username\": \"${1}\"" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=username= ) + local username + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + sudo yunohost user list --output-as json | grep -q "\"username\": \"${username}\"" } # Retrieve a YunoHost user information # # example: mail=$(ynh_user_get_info 'toto' 'mail') # -# usage: ynh_user_get_info username key -# | arg: username - the username to retrieve info from -# | arg: key - the key to retrieve +# usage: ynh_user_get_info --username=username --key=key +# | arg: -u, --username - the username to retrieve info from +# | arg: -k, --key - the key to retrieve # | ret: string - the key's value ynh_user_get_info() { - sudo yunohost user info "$1" --output-as plain | ynh_get_plain_key "$2" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=username= [k]=key= ) + local username + local key + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + sudo yunohost user info "$username" --output-as plain | ynh_get_plain_key "$key" } # Get the list of YunoHost users @@ -33,39 +46,58 @@ ynh_user_list() { # Check if a user exists on the system # -# usage: ynh_system_user_exists username -# | arg: username - the username to check +# usage: ynh_system_user_exists --username=username +# | arg: -u, --username - the username to check ynh_system_user_exists() { - getent passwd "$1" &>/dev/null + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=username= ) + local username + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + getent passwd "$username" &>/dev/null } # Create a system user # -# usage: ynh_system_user_create user_name [home_dir] -# | arg: user_name - Name of the system user that will be create -# | arg: home_dir - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home +# usage: ynh_system_user_create --username=user_name [--home_dir=home_dir] +# | arg: -u, --username - Name of the system user that will be create +# | arg: -h, --home_dir - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home ynh_system_user_create () { - if ! ynh_system_user_exists "$1" # Check if the user exists on the system + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=username= [h]=home_dir= ) + local username + local home_dir + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + if ! ynh_system_user_exists "$username" # Check if the user exists on the system then # If the user doesn't exist if [ $# -ge 2 ]; then # If a home dir is mentioned - local user_home_dir="-d $2" + local user_home_dir="-d $home_dir" else local user_home_dir="--no-create-home" fi - sudo useradd $user_home_dir --system --user-group $1 --shell /usr/sbin/nologin || ynh_die "Unable to create $1 system account" + sudo useradd $user_home_dir --system --user-group $username --shell /usr/sbin/nologin || ynh_die "Unable to create $username system account" fi } # Delete a system user # -# usage: ynh_system_user_delete user_name -# | arg: user_name - Name of the system user that will be create +# usage: ynh_system_user_delete --username=user_name +# | arg: -u, --username - Name of the system user that will be create ynh_system_user_delete () { - if ynh_system_user_exists "$1" # Check if the user exists on the system + # Declare an array to define the options of this helper. + declare -Ar args_array=( [u]=username= ) + local username + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + if ynh_system_user_exists "$username" # Check if the user exists on the system then - echo "Remove the user $1" >&2 - sudo userdel $1 + echo "Remove the user $username" >&2 + sudo userdel $username else - echo "The user $1 was not found" >&2 + echo "The user $username was not found" >&2 fi } From bc08c5c872dda353d3079a010c7fb18a6c0c845a Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:17:08 +0100 Subject: [PATCH 036/721] Use getopts for ynh_setup_source helper --- data/helpers.d/utils | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 07b8a6d96..339d51256 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -141,22 +141,27 @@ ynh_backup_before_upgrade () { # sources/extra_files/$src_id will be applied to dest_dir # # -# usage: ynh_setup_source dest_dir [source_id] -# | arg: dest_dir - Directory where to setup sources -# | arg: source_id - Name of the app, if the package contains more than one app +# usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] +# | arg: -d, --dest_dir - Directory where to setup sources +# | arg: -s, --source_id - Name of the app, if the package contains more than one app ynh_setup_source () { - local dest_dir=$1 - local src_id=${2:-app} # If the argument is not given, source_id equals "app" + # Declare an array to define the options of this helper. + declare -Ar args_array=( [d]=dest_dir= [s]=source_id= ) + local dest_dir + local source_id + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + source_id="${source_id:-app}" # If the argument is not given, source_id equals "app" # Load value from configuration file (see above for a small doc about this file # format) - local src_url=$(grep 'SOURCE_URL=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) - local src_sum=$(grep 'SOURCE_SUM=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) - local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) - local src_format=$(grep 'SOURCE_FORMAT=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) - local src_extract=$(grep 'SOURCE_EXTRACT=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) - local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) - local src_filename=$(grep 'SOURCE_FILENAME=' "$YNH_CWD/../conf/${src_id}.src" | cut -d= -f2-) + local src_url=$(grep 'SOURCE_URL=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) + local src_sum=$(grep 'SOURCE_SUM=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) + local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) + local src_format=$(grep 'SOURCE_FORMAT=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) + local src_extract=$(grep 'SOURCE_EXTRACT=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) + local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) + local src_filename=$(grep 'SOURCE_FILENAME=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) # Default value src_sumprg=${src_sumprg:-sha256sum} @@ -165,7 +170,7 @@ ynh_setup_source () { src_format=$(echo "$src_format" | tr '[:upper:]' '[:lower:]') src_extract=${src_extract:-true} if [ "$src_filename" = "" ] ; then - src_filename="${src_id}.${src_format}" + src_filename="${source_id}.${src_format}" fi local local_src="/opt/yunohost-apps-src/${YNH_APP_ID}/${src_filename}" @@ -211,18 +216,18 @@ ynh_setup_source () { fi # Apply patches - if (( $(find $YNH_CWD/../sources/patches/ -type f -name "${src_id}-*.patch" 2> /dev/null | wc -l) > "0" )); then + if (( $(find $YNH_CWD/../sources/patches/ -type f -name "${source_id}-*.patch" 2> /dev/null | wc -l) > "0" )); then local old_dir=$(pwd) (cd "$dest_dir" \ - && for p in $YNH_CWD/../sources/patches/${src_id}-*.patch; do \ + && for p in $YNH_CWD/../sources/patches/${source_id}-*.patch; do \ patch -p1 < $p; done) \ || ynh_die "Unable to apply patches" cd $old_dir fi # Add supplementary files - if test -e "$YNH_CWD/../sources/extra_files/${src_id}"; then - cp -a $YNH_CWD/../sources/extra_files/$src_id/. "$dest_dir" + if test -e "$YNH_CWD/../sources/extra_files/${source_id}"; then + cp -a $YNH_CWD/../sources/extra_files/$source_id/. "$dest_dir" fi } From d3a501aa416221c91ebc1154bef572e682e39c52 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:50:54 +0100 Subject: [PATCH 037/721] Use getopts helpers in string --- data/helpers.d/string | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/string b/data/helpers.d/string index c13497f9f..188c2da62 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -19,7 +19,7 @@ ynh_string_random() { # Substitute/replace a string (or expression) by another in a file # -# usage: ynh_replace_string match_string replace_string target_file +# usage: ynh_replace_string --match_string=match_string --replace_string=replace_string --target_file=target_file # | arg: -m, --match_string - String to be searched and replaced in the file # | arg: -r, --replace_string - String that will replace matches # | arg: -f, --target_file - File in which the string will be replaced. @@ -46,7 +46,7 @@ ynh_replace_string () { # Substitute/replace a special string by another in a file # -# usage: ynh_replace_special_string match_string replace_string target_file +# usage: ynh_replace_special_string --match_string=match_string --replace_string=replace_string --target_file=target_file # | arg: -m, --match_string - String to be searched and replaced in the file # | arg: -r, --replace_string - String that will replace matches # | arg: -t, --target_file - File in which the string will be replaced. @@ -70,5 +70,5 @@ ynh_replace_special_string () { match_string=${match_string//&/"\&"} replace_string=${replace_string//&/"\&"} - ynh_replace_string "$match_string" "$replace_string" "$target_file" + ynh_replace_string --match_string="$match_string" --replace_string="$replace_string" --target_file="$target_file" } From 8c600e1e0ba2139a60854c8acbf3b11c59362a8e Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:51:12 +0100 Subject: [PATCH 038/721] Use getopts helpers in mysql --- data/helpers.d/mysql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 0cfe65380..4898bed92 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -176,7 +176,7 @@ ynh_mysql_setup_db () { db_password="${db_password:-$new_db_pwd}" ynh_mysql_create_db "$db_name" "$db_user" "$db_password" # Create the database - ynh_app_setting_set $app mysqlpwd $db_password # Store the password in the app's config + ynh_app_setting_set --app=$app --key=mysqlpwd --value=$db_password # Store the password in the app's config } # Remove a database if it exists, and the associated user From 43e4a34d212737161bcc7a127f32bba7fd6ff697 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:51:21 +0100 Subject: [PATCH 039/721] Use getopts helpers in network --- data/helpers.d/network | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/network b/data/helpers.d/network index fc7eb3f69..0059702e9 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -17,7 +17,7 @@ ynh_normalize_url_path () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - test -n "$path_url" || ynh_die "ynh_normalize_url_path expect a URL path as first argument and received nothing." + test -n "$path_url" || ynh_die --message="ynh_normalize_url_path expect a URL path as first argument and received nothing." if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a / path_url="/$path_url" # Add / at begin of path variable fi @@ -40,7 +40,7 @@ ynh_find_port () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - test -n "$port" || ynh_die "The argument of ynh_find_port must be a valid port." + test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." while netcat -z 127.0.0.1 $port # Check if the port is free do port=$((port+1)) # Else, pass to next port From 68d6adf8100a43e7e045746e9045a13fc03e0207 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:51:34 +0100 Subject: [PATCH 040/721] Use getopts helpers in package --- data/helpers.d/package | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/package b/data/helpers.d/package index 6a0f9d1c7..cb5de5f06 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -121,7 +121,7 @@ ynh_package_install_from_equivs () { && equivs-build ./control 1>/dev/null \ && sudo dpkg --force-depends \ -i "./${pkgname}_${pkgversion}_all.deb" 2>&1 \ - && ynh_package_install -f) || ynh_die "Unable to install dependencies" + && ynh_package_install -f) || ynh_die --message="Unable to install dependencies" [[ -n "$TMPDIR" ]] && rm -rf $TMPDIR # Remove the temp dir. # check if the package is actually installed @@ -162,9 +162,9 @@ Description: Fake package for ${app} (YunoHost app) dependencies This meta-package is only responsible of installing its dependencies. EOF ynh_package_install_from_equivs /tmp/${dep_app}-ynh-deps.control \ - || ynh_die "Unable to install dependencies" # Install the fake package and its dependencies + || ynh_die --message="Unable to install dependencies" # Install the fake package and its dependencies rm /tmp/${dep_app}-ynh-deps.control - ynh_app_setting_set $app apt_dependencies $dependencies + ynh_app_setting_set --app=$app --key=apt_dependencies --value="$dependencies" } # Remove fake package and its dependencies From 0aa8a2ede354d4c1cf03576549d0aeebe3e6cdb8 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:51:39 +0100 Subject: [PATCH 041/721] Use getopts helpers in user --- data/helpers.d/user | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index 4a2e7c55d..3947ebe7f 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -78,7 +78,7 @@ ynh_system_user_create () { else local user_home_dir="--no-create-home" fi - sudo useradd $user_home_dir --system --user-group $username --shell /usr/sbin/nologin || ynh_die "Unable to create $username system account" + sudo useradd $user_home_dir --system --user-group $username --shell /usr/sbin/nologin || ynh_die --message="Unable to create $username system account" fi } From 2094557e1e32f7b8d56406d4229fcd8e1f04f1fd Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:51:49 +0100 Subject: [PATCH 042/721] Use getopts helpers in getopts itself --- data/helpers.d/getopts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index 1cc66da8a..827e08477 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -98,10 +98,10 @@ ynh_handle_getopts_args () { if [ "$parameter" = "?" ] then - ynh_die "Invalid argument: -${OPTARG:-}" + ynh_die --message="Invalid argument: -${OPTARG:-}" elif [ "$parameter" = ":" ] then - ynh_die "-$OPTARG parameter requires an argument." + ynh_die --message="-$OPTARG parameter requires an argument." else local shift_value=1 # Use the long option, corresponding to the short option read by getopts, as a variable From 2c6b76e099a2e1f81feb2926dee869472d3e2b6f Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:52:03 +0100 Subject: [PATCH 043/721] Use getopts helpers in nodejs --- data/helpers.d/nodejs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index d56ef8272..fcba2d75a 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -15,7 +15,7 @@ ynh_install_n () { echo "SOURCE_URL=https://github.com/tj/n/archive/v2.1.7.tar.gz SOURCE_SUM=2ba3c9d4dd3c7e38885b37e02337906a1ee91febe6d5c9159d89a9050f2eea8f" > "../conf/n.src" # Download and extract n - ynh_setup_source "$n_install_dir/git" n + ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n # Install n (cd "$n_install_dir/git" PREFIX=$N_PREFIX make install 2>&1) @@ -35,7 +35,7 @@ SOURCE_SUM=2ba3c9d4dd3c7e38885b37e02337906a1ee91febe6d5c9159d89a9050f2eea8f" > " # # usage: ynh_use_nodejs ynh_use_nodejs () { - nodejs_version=$(ynh_app_setting_get $app nodejs_version) + nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) nodejs_use_version="echo \"Deprecated command, should be removed\"" @@ -85,7 +85,7 @@ ynh_install_nodejs () { fi # Modify the default N_PREFIX in n script - ynh_replace_string "^N_PREFIX=\${N_PREFIX-.*}$" "N_PREFIX=\${N_PREFIX-$N_PREFIX}" "$n_install_dir/bin/n" + ynh_replace_string --match_string="^N_PREFIX=\${N_PREFIX-.*}$" --replace_string="N_PREFIX=\${N_PREFIX-$N_PREFIX}" --target_file="$n_install_dir/bin/n" # Restore /usr/local/bin in PATH PATH=$CLEAR_PATH @@ -111,7 +111,7 @@ ynh_install_nodejs () { echo "$YNH_APP_ID:$nodejs_version" | tee --append "$n_install_dir/ynh_app_version" # Store nodejs_version into the config of this app - ynh_app_setting_set $app nodejs_version $nodejs_version + ynh_app_setting_set --app=$app --key=nodejs_version --value=$nodejs_version # Build the update script and set the cronjob ynh_cron_upgrade_node @@ -127,7 +127,7 @@ ynh_install_nodejs () { # # usage: ynh_remove_nodejs ynh_remove_nodejs () { - nodejs_version=$(ynh_app_setting_get $app nodejs_version) + nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) # Remove the line for this app sed --in-place "/$YNH_APP_ID:$nodejs_version/d" "$n_install_dir/ynh_app_version" @@ -141,8 +141,8 @@ ynh_remove_nodejs () { # If no other app uses n, remove n if [ ! -s "$n_install_dir/ynh_app_version" ] then - ynh_secure_remove "$n_install_dir" - ynh_secure_remove "/usr/local/n" + ynh_secure_remove --file="$n_install_dir" + ynh_secure_remove --file="/usr/local/n" sed --in-place "/N_PREFIX/d" /root/.bashrc rm -f /etc/cron.daily/node_update fi From 80c0b7bdf2261b3476217ce70b9618f850a65727 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:52:12 +0100 Subject: [PATCH 044/721] Use getopts helpers in utils --- data/helpers.d/utils | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 339d51256..b6d7c11f6 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -49,7 +49,7 @@ ynh_restore_upgradebackup () { sudo yunohost app remove $app # Restore the backup sudo yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force - ynh_die "The app was restored to the way it was before the failed upgrade." + ynh_die --message="The app was restored to the way it was before the failed upgrade." fi else echo "\$NO_BACKUP_UPGRADE is set, that means there's no backup to restore. You have to fix this upgrade by yourself !" >&2 @@ -97,7 +97,7 @@ ynh_backup_before_upgrade () { sudo yunohost backup delete $app_bck-pre-upgrade$old_backup_number > /dev/null fi else - ynh_die "Backup failed, the upgrade process was aborted." + ynh_die --message="Backup failed, the upgrade process was aborted." fi else echo "\$NO_BACKUP_UPGRADE is set, backup will be avoided. Be careful, this upgrade is going to be operated without a security backup" @@ -178,12 +178,12 @@ ynh_setup_source () { then # Use the local source file if it is present cp $local_src $src_filename else # If not, download the source - local out=`wget -nv -O $src_filename $src_url 2>&1` || ynh_print_err $out + local out=`wget -nv -O $src_filename $src_url 2>&1` || ynh_print_err --message="$out" fi # Check the control sum echo "${src_sum} ${src_filename}" | ${src_sumprg} -c --status \ - || ynh_die "Corrupt source" + || ynh_die --message="Corrupt source" # Extract source into the app dir mkdir -p "$dest_dir" @@ -199,7 +199,7 @@ ynh_setup_source () { local tmp_dir=$(mktemp -d) unzip -quo $src_filename -d "$tmp_dir" cp -a $tmp_dir/*/. "$dest_dir" - ynh_secure_remove "$tmp_dir" + ynh_secure_remove --file="$tmp_dir" else unzip -quo $src_filename -d "$dest_dir" fi @@ -211,7 +211,7 @@ ynh_setup_source () { if [[ "$src_format" =~ ^tar.gz|tar.bz2|tar.xz$ ]] ; then tar -xf $src_filename -C "$dest_dir" $strip else - ynh_die "Archive format unrecognized." + ynh_die --message="Archive format unrecognized." fi fi @@ -221,7 +221,7 @@ ynh_setup_source () { (cd "$dest_dir" \ && for p in $YNH_CWD/../sources/patches/${source_id}-*.patch; do \ patch -p1 < $p; done) \ - || ynh_die "Unable to apply patches" + || ynh_die --message="Unable to apply patches" cd $old_dir fi From 705fe435ed43a26048a9348e3f86891e701db069 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:52:23 +0100 Subject: [PATCH 045/721] Use getopts helpers in backend --- data/helpers.d/backend | 60 +++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 025ce45f0..840fcfd6a 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -122,18 +122,18 @@ ynh_add_systemd_config () { local template="${nonappend:-systemd.service}" finalsystemdconf="/etc/systemd/system/$service.service" - ynh_backup_if_checksum_is_different "$finalsystemdconf" + ynh_backup_if_checksum_is_different --file="$finalsystemdconf" sudo cp ../conf/$template "$finalsystemdconf" # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. # Substitute in a nginx config file only if the variable is not empty if test -n "${final_path:-}"; then - ynh_replace_string "__FINALPATH__" "$final_path" "$finalsystemdconf" + ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalsystemdconf" fi if test -n "${app:-}"; then - ynh_replace_string "__APP__" "$app" "$finalsystemdconf" + ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$finalsystemdconf" fi - ynh_store_file_checksum "$finalsystemdconf" + ynh_store_file_checksum --file="$finalsystemdconf" sudo chown root: "$finalsystemdconf" sudo systemctl enable $service @@ -157,7 +157,7 @@ ynh_remove_systemd_config () { if [ -e "$finalsystemdconf" ]; then sudo systemctl stop $service sudo systemctl disable $service - ynh_secure_remove "$finalsystemdconf" + ynh_secure_remove --file="$finalsystemdconf" sudo systemctl daemon-reload fi } @@ -183,7 +183,7 @@ ynh_remove_systemd_config () { ynh_add_nginx_config () { finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" local others_var=${1:-} - ynh_backup_if_checksum_is_different "$finalnginxconf" + ynh_backup_if_checksum_is_different --file="$finalnginxconf" sudo cp ../conf/nginx.conf "$finalnginxconf" # To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable. @@ -191,20 +191,20 @@ ynh_add_nginx_config () { if test -n "${path_url:-}"; then # path_url_slash_less is path_url, or a blank value if path_url is only '/' local path_url_slash_less=${path_url%/} - ynh_replace_string "__PATH__/" "$path_url_slash_less/" "$finalnginxconf" - ynh_replace_string "__PATH__" "$path_url" "$finalnginxconf" + ynh_replace_string --match_string="__PATH__/" --replace_string="$path_url_slash_less/" --target_file="$finalnginxconf" + ynh_replace_string --match_string="__PATH__" --replace_string="$path_url" --target_file="$finalnginxconf" fi if test -n "${domain:-}"; then - ynh_replace_string "__DOMAIN__" "$domain" "$finalnginxconf" + ynh_replace_string --match_string="__DOMAIN__" --replace_string="$domain" --target_file="$finalnginxconf" fi if test -n "${port:-}"; then - ynh_replace_string "__PORT__" "$port" "$finalnginxconf" + ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$finalnginxconf" fi if test -n "${app:-}"; then - ynh_replace_string "__NAME__" "$app" "$finalnginxconf" + ynh_replace_string --match_string="__NAME__" --replace_string="$app" --target_file="$finalnginxconf" fi if test -n "${final_path:-}"; then - ynh_replace_string "__FINALPATH__" "$final_path" "$finalnginxconf" + ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalnginxconf" fi # Replace all other variable given as arguments @@ -212,17 +212,17 @@ ynh_add_nginx_config () { do # ${var_to_replace^^} make the content of the variable on upper-cases # ${!var_to_replace} get the content of the variable named $var_to_replace - ynh_replace_string "__${var_to_replace^^}__" "${!var_to_replace}" "$finalnginxconf" + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalnginxconf" done if [ "${path_url:-}" != "/" ] then - ynh_replace_string "^#sub_path_only" "" "$finalnginxconf" + ynh_replace_string --match_string="^#sub_path_only" --replace_string="" --target_file="$finalnginxconf" else - ynh_replace_string "^#root_path_only" "" "$finalnginxconf" + ynh_replace_string --match_string="^#root_path_only" --replace_string="" --target_file="$finalnginxconf" fi - ynh_store_file_checksum "$finalnginxconf" + ynh_store_file_checksum --file="$finalnginxconf" sudo systemctl reload nginx } @@ -231,7 +231,7 @@ ynh_add_nginx_config () { # # usage: ynh_remove_nginx_config ynh_remove_nginx_config () { - ynh_secure_remove "/etc/nginx/conf.d/$domain.d/$app.conf" + ynh_secure_remove --file="/etc/nginx/conf.d/$domain.d/$app.conf" sudo systemctl reload nginx } @@ -247,24 +247,24 @@ ynh_add_fpm_config () { fpm_config_dir="/etc/php5/fpm" fpm_service="php5-fpm" fi - ynh_app_setting_set $app fpm_config_dir "$fpm_config_dir" - ynh_app_setting_set $app fpm_service "$fpm_service" + ynh_app_setting_set --app=$app --key=fpm_config_dir --value="$fpm_config_dir" + ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service" finalphpconf="$fpm_config_dir/pool.d/$app.conf" - ynh_backup_if_checksum_is_different "$finalphpconf" + ynh_backup_if_checksum_is_different --file="$finalphpconf" sudo cp ../conf/php-fpm.conf "$finalphpconf" - ynh_replace_string "__NAMETOCHANGE__" "$app" "$finalphpconf" - ynh_replace_string "__FINALPATH__" "$final_path" "$finalphpconf" - ynh_replace_string "__USER__" "$app" "$finalphpconf" + ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" + ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" + ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf" sudo chown root: "$finalphpconf" - ynh_store_file_checksum "$finalphpconf" + ynh_store_file_checksum --file="$finalphpconf" if [ -e "../conf/php-fpm.ini" ] then finalphpini="$fpm_config_dir/conf.d/20-$app.ini" - ynh_backup_if_checksum_is_different "$finalphpini" + ynh_backup_if_checksum_is_different --file="$finalphpini" sudo cp ../conf/php-fpm.ini "$finalphpini" sudo chown root: "$finalphpini" - ynh_store_file_checksum "$finalphpini" + ynh_store_file_checksum --file="$finalphpini" fi sudo systemctl reload $fpm_service } @@ -273,14 +273,14 @@ ynh_add_fpm_config () { # # usage: ynh_remove_fpm_config ynh_remove_fpm_config () { - local fpm_config_dir=$(ynh_app_setting_get $app fpm_config_dir) - local fpm_service=$(ynh_app_setting_get $app fpm_service) + local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) + local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service) # Assume php version 5 if not set if [ -z "$fpm_config_dir" ]; then fpm_config_dir="/etc/php5/fpm" fpm_service="php5-fpm" fi - ynh_secure_remove "$fpm_config_dir/pool.d/$app.conf" - ynh_secure_remove "$fpm_config_dir/conf.d/20-$app.ini" 2>&1 + ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" + ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1 sudo systemctl reload $fpm_service } From e3bebf017858a5c979fa7121e396c20fe0c3a130 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 29 Dec 2018 18:52:39 +0100 Subject: [PATCH 046/721] Use getopts helpers in filesystem --- data/helpers.d/filesystem | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 34b0487cc..4cb60f7cb 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -145,7 +145,7 @@ ynh_restore () { while read line; do local ORIGIN_PATH=$(echo "$line" | grep -ohP "^\"\K.*(?=\",\".*\"$)") local ARCHIVE_PATH=$(echo "$line" | grep -ohP "^\".*\",\"$REL_DIR\K.*(?=\"$)") - ynh_restore_file "$ARCHIVE_PATH" "$ORIGIN_PATH" + ynh_restore_file --origin_path="$ARCHIVE_PATH" --dest_path="$ORIGIN_PATH" done } @@ -223,7 +223,7 @@ ynh_restore_file () { mkdir -p "$(dirname "$backup_file")" mv "${dest_path}" "$backup_file" # Move the current file or directory else - ynh_secure_remove ${dest_path} + ynh_secure_remove --file=${dest_path} fi fi @@ -289,7 +289,7 @@ ynh_store_file_checksum () { ynh_handle_getopts_args "$@" local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' - ynh_app_setting_set $app $checksum_setting_name $(sudo md5sum "$file" | cut -d' ' -f1) + ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(sudo md5sum "$file" | cut -d' ' -f1) } # Verify the checksum and backup the file if it's different @@ -310,7 +310,7 @@ ynh_backup_if_checksum_is_different () { ynh_handle_getopts_args "$@" local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' - local checksum_value=$(ynh_app_setting_get $app $checksum_setting_name) + local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name) if [ -n "$checksum_value" ] then # Proceed only if a value was stored into the app settings if ! echo "$checksum_value $file" | sudo md5sum -c --status @@ -338,12 +338,12 @@ ynh_delete_file_checksum () { ynh_handle_getopts_args "$@" local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' - ynh_app_setting_delete $app $checksum_setting_name + ynh_app_setting_delete --app=$app --key=$checksum_setting_name } # Remove a file or a directory securely # -# usage: ynh_secure_remove file=path_to_remove +# usage: ynh_secure_remove --file=path_to_remove # | arg: -f, --file - File or directory to remove ynh_secure_remove () { # Declare an array to define the options of this helper. From 4282d7edca6d6785d4a7263b896c29245657bd77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 28 Dec 2018 16:48:42 +0100 Subject: [PATCH 047/721] Fix tests --- src/yunohost/tests/test_appurl.py | 6 +++--- src/yunohost/tests/test_backuprestore.py | 2 +- src/yunohost/tests/test_changeurl.py | 2 +- src/yunohost/utils/error.py | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index d9d5fa7ab..3a3a1db35 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -51,18 +51,18 @@ def test_urlavailable(): def test_registerurl(): app_install(auth, "./tests/apps/register_url_app_ynh", - args="domain=%s&path=%s" % (maindomain, "/urlregisterapp")) + args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"), force=True) assert not domain_url_available(auth, maindomain, "/urlregisterapp") # Try installing at same location with pytest.raises(YunohostError): app_install(auth, "./tests/apps/register_url_app_ynh", - args="domain=%s&path=%s" % (maindomain, "/urlregisterapp")) + args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"), force=True) def test_registerurl_baddomain(): with pytest.raises(YunohostError): app_install(auth, "./tests/apps/register_url_app_ynh", - args="domain=%s&path=%s" % ("yolo.swag", "/urlregisterapp")) + args="domain=%s&path=%s" % ("yolo.swag", "/urlregisterapp"), force=True) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index af8538dae..14c479d9a 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -171,7 +171,7 @@ def install_app(app, path, additionnal_args=""): app_install(auth, "./tests/apps/%s" % app, args="domain=%s&path=%s%s" % (maindomain, path, - additionnal_args)) + additionnal_args), force=True) def add_archive_wordpress_from_2p4(): diff --git a/src/yunohost/tests/test_changeurl.py b/src/yunohost/tests/test_changeurl.py index 4856e18c1..6475e2e21 100644 --- a/src/yunohost/tests/test_changeurl.py +++ b/src/yunohost/tests/test_changeurl.py @@ -28,7 +28,7 @@ def teardown_function(function): def install_changeurl_app(path): app_install(auth, "./tests/apps/change_url_app_ynh", - args="domain=%s&path=%s" % (maindomain, path)) + args="domain=%s&path=%s" % (maindomain, path), force=True) def check_changeurl_app(path): diff --git a/src/yunohost/utils/error.py b/src/yunohost/utils/error.py index c7108a6ba..aeffabcf0 100644 --- a/src/yunohost/utils/error.py +++ b/src/yunohost/utils/error.py @@ -32,9 +32,9 @@ class YunohostError(MoulinetteError): are translated via m18n.n (namespace) instead of m18n.g (global?) """ - def __init__(self, key, __raw_msg__=False, *args, **kwargs): - if __raw_msg__: + def __init__(self, key, raw_msg=False, *args, **kwargs): + if raw_msg: msg = key else: msg = m18n.n(key, *args, **kwargs) - super(YunohostError, self).__init__(msg, __raw_msg__=True) + super(YunohostError, self).__init__(msg, raw_msg=True) From 9da00bd8b670ede3d48f2b9cef85c7d2ef723db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 30 Dec 2018 17:49:12 +0100 Subject: [PATCH 048/721] Fix change url function --- src/yunohost/app.py | 24 ++++++------------------ src/yunohost/domain.py | 2 +- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 11f92afd1..9f7f196f0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -445,6 +445,7 @@ def app_change_url(operation_logger, auth, app, domain, path): """ from yunohost.hook import hook_exec, hook_callback + from yunohost.domain import _normalize_domain_path, _get_conflicting_apps installed = _is_installed(app) if not installed: @@ -457,18 +458,13 @@ def app_change_url(operation_logger, auth, app, domain, path): old_path = app_setting(app, "path") # Normalize path and domain format - domain = domain.strip().lower() - - old_path = normalize_url_path(old_path) - path = normalize_url_path(path) + old_domain, old_path = _normalize_domain_path(old_domain, old_path) + domain, path = _normalize_domain_path(domain, path) if (domain, path) == (old_domain, old_path): raise YunohostError("app_change_url_identical_domains", domain=domain, path=path) - # WARNING / FIXME : checkurl will modify the settings - # (this is a non intuitive behavior that should be changed) - # (or checkurl renamed in reserve_url) - app_checkurl(auth, '%s%s' % (domain, path), app) + _get_conflicting_apps(auth, domain, path) manifest = json.load(open(os.path.join(APPS_SETTING_PATH, app, "manifest.json"))) @@ -486,9 +482,9 @@ def app_change_url(operation_logger, auth, app, domain, path): env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) env_dict["YNH_APP_OLD_DOMAIN"] = old_domain - env_dict["YNH_APP_OLD_PATH"] = old_path.rstrip("/") + env_dict["YNH_APP_OLD_PATH"] = old_path env_dict["YNH_APP_NEW_DOMAIN"] = domain - env_dict["YNH_APP_NEW_PATH"] = path.rstrip("/") + env_dict["YNH_APP_NEW_PATH"] = path if domain != old_domain: operation_logger.related_to.append(('domain', old_domain)) @@ -1251,7 +1247,6 @@ def app_register_url(auth, app, domain, path): # We cannot change the url of an app already installed simply by changing # the settings... - # FIXME should look into change_url once it's merged installed = app in app_list(installed=True, raw=True).keys() if installed: @@ -2529,13 +2524,6 @@ def random_password(length=8): return ''.join([random.SystemRandom().choice(char_set) for x in range(length)]) -def normalize_url_path(url_path): - if url_path.strip("/").strip(): - return '/' + url_path.strip("/").strip() + '/' - - return "/" - - def unstable_apps(): raw_app_installed = app_list(installed=True, raw=True) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 52ddc84b3..16d391168 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -298,7 +298,7 @@ def _normalize_domain_path(domain, path): domain = domain[len("http://"):] # Remove trailing slashes - domain = domain.rstrip("/") + domain = domain.rstrip("/").lower() path = "/" + path.strip("/") return domain, path From 616b0d020bf26bd05e28698d8caa78b654972b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 30 Dec 2018 18:58:27 +0100 Subject: [PATCH 049/721] Fix tests --- src/yunohost/tests/test_changeurl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_changeurl.py b/src/yunohost/tests/test_changeurl.py index 4856e18c1..728042445 100644 --- a/src/yunohost/tests/test_changeurl.py +++ b/src/yunohost/tests/test_changeurl.py @@ -34,9 +34,9 @@ def install_changeurl_app(path): def check_changeurl_app(path): appmap = app_map(raw=True) - assert path + "/" in appmap[maindomain].keys() + assert path in appmap[maindomain].keys() - assert appmap[maindomain][path + "/"]["id"] == "change_url_app" + assert appmap[maindomain][path]["id"] == "change_url_app" r = requests.get("https://127.0.0.1%s/" % path, headers={"domain": maindomain}, verify=False) assert r.status_code == 200 From 412c656a9b2c928f6abd2d4411060ba18240a332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 30 Dec 2018 18:58:40 +0100 Subject: [PATCH 050/721] Fix change_url too --- src/yunohost/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 9f7f196f0..bde39685c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1284,7 +1284,7 @@ def app_checkurl(auth, url, app=None): logger.error("Packagers /!\\ : 'app checkurl' is deprecated ! Please use the helper 'ynh_webpath_register' instead !") - from yunohost.domain import domain_list + from yunohost.domain import domain_list, _normalize_domain_path if "https://" == url[:8]: url = url[8:] @@ -1298,8 +1298,7 @@ def app_checkurl(auth, url, app=None): path = url[url.index('/'):] installed = False - if path[-1:] != '/': - path = path + '/' + domain, path = _normalize_domain_path(domain, path) apps_map = app_map(raw=True) From d656d5b4ba35c7ad4da46fc0c29a9c53fe326c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 30 Dec 2018 22:32:47 +0100 Subject: [PATCH 051/721] Raise a error is path is not available --- src/yunohost/app.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index bde39685c..3e7fb0c3f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -464,7 +464,18 @@ def app_change_url(operation_logger, auth, app, domain, path): if (domain, path) == (old_domain, old_path): raise YunohostError("app_change_url_identical_domains", domain=domain, path=path) - _get_conflicting_apps(auth, domain, path) + # Check the url is available + conflicts = _get_conflicting_apps(auth, domain, path) + if conflicts: + apps = [] + for path, app_id, app_label in conflicts: + apps.append(" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format( + domain=domain, + path=path, + app_id=app_id, + app_label=app_label, + )) + raise YunohostError('app_location_unavailable', apps="\n".join(apps)) manifest = json.load(open(os.path.join(APPS_SETTING_PATH, app, "manifest.json"))) From 6a7990d7fb251eb254d37fefee1d6399087ce528 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 4 Jan 2019 16:25:49 +0100 Subject: [PATCH 052/721] [fix] Weird duplicated code + fix missing key file triggering an error --- data/hooks/conf_regen/03-ssh | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index 34cb441b4..9de527518 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -16,17 +16,11 @@ do_pre_regen() { # do not listen to IPv6 if unavailable [[ -f /proc/net/if_inet6 ]] && ipv6_enabled=true || ipv6_enabled=false - # Support legacy setting (this setting might be disabled by a user during a migration) - ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null) - if [[ "$(yunohost settings get 'service.ssh.allow_deprecated_dsa_hostkey')" == "True" ]]; then - ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null)" - fi - - ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null) + ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null || true) # Support legacy setting (this setting might be disabled by a user during a migration) if [[ "$(yunohost settings get 'service.ssh.allow_deprecated_dsa_hostkey')" == "True" ]]; then - ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null)" + ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null || true)" fi export ssh_keys From 976f160afbf34be27b04cd0bc1dc13870d642848 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 4 Jan 2019 17:11:49 +0100 Subject: [PATCH 053/721] Fix getopts and its legacy mode --- data/helpers.d/backend | 3 +++ data/helpers.d/filesystem | 8 ++++++++ data/helpers.d/getopts | 42 +++++++++++++++++++++------------------ data/helpers.d/ip | 5 +++++ data/helpers.d/mysql | 10 ++++++++++ data/helpers.d/network | 6 ++++++ data/helpers.d/nodejs | 3 +++ data/helpers.d/package | 4 ++++ data/helpers.d/print | 6 ++++++ data/helpers.d/setting | 5 +++++ data/helpers.d/string | 5 +++++ data/helpers.d/system | 2 ++ data/helpers.d/user | 7 +++++++ data/helpers.d/utils | 3 +++ 14 files changed, 90 insertions(+), 19 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 12e2b1964..2109b17ca 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -16,6 +16,7 @@ # Unless you use the option --non-append ynh_use_logrotate () { # Declare an array to define the options of this helper. + local legacy_args=lnuya declare -Ar args_array=( [l]=logfile= [n]=nonappend [u]=specific_user= [y]=non [a]=append ) # [y]=non [a]=append are only for legacy purpose, to not fail on the old option '--non-append' local logfile @@ -113,6 +114,7 @@ ynh_remove_logrotate () { # ynh_add_systemd_config () { # Declare an array to define the options of this helper. + local legacy_args=st declare -Ar args_array=( [s]=service= [t]=template= ) local service local template @@ -147,6 +149,7 @@ ynh_add_systemd_config () { # ynh_remove_systemd_config () { # Declare an array to define the options of this helper. + local legacy_args=s declare -Ar args_array=( [s]=service= ) local service # Manage arguments with getopts diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 55bad87e8..2e9f93166 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -1,3 +1,5 @@ +#!/bin/bash + CAN_BIND=${CAN_BIND:-1} # Add a file or a directory to the list of paths to backup @@ -46,6 +48,7 @@ ynh_backup() { # TODO find a way to avoid injection by file strange naming ! # Declare an array to define the options of this helper. + local legacy_args=sdbm declare -Ar args_array=( [s]=src_path= [d]=dest_path= [b]=is_big [m]=not_mandatory ) local src_path local dest_path @@ -214,6 +217,7 @@ with open(sys.argv[1], 'r') as backup_file: # ynh_restore_file () { # Declare an array to define the options of this helper. + local legacy_args=odm declare -Ar args_array=( [o]=origin_path= [d]=dest_path= [m]=not_mandatory ) local origin_path local archive_path @@ -307,6 +311,7 @@ properly with chmod/chown." >&2 # | arg: -f, --file - The file on which the checksum will performed, then stored. ynh_store_file_checksum () { # Declare an array to define the options of this helper. + local legacy_args=f declare -Ar args_array=( [f]=file= ) local file # Manage arguments with getopts @@ -328,6 +333,7 @@ ynh_store_file_checksum () { # | ret: Return the name a the backup file, or nothing ynh_backup_if_checksum_is_different () { # Declare an array to define the options of this helper. + local legacy_args=f declare -Ar args_array=( [f]=file= ) local file # Manage arguments with getopts @@ -356,6 +362,7 @@ ynh_backup_if_checksum_is_different () { # | arg: -f, --file= - The file for which the checksum will be deleted ynh_delete_file_checksum () { # Declare an array to define the options of this helper. + local legacy_args=f declare -Ar args_array=( [f]=file= ) local file # Manage arguments with getopts @@ -371,6 +378,7 @@ ynh_delete_file_checksum () { # | arg: -f, --file - File or directory to remove ynh_secure_remove () { # Declare an array to define the options of this helper. + local legacy_args=f declare -Ar args_array=( [f]=file= ) local file # Manage arguments with getopts diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index b9897f7d9..e9fbe18e8 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -53,33 +53,33 @@ ynh_handle_getopts_args () { # For each option in the array, reduce to short options for getopts (e.g. for [u]=user, --user will be -u) # And built parameters string for getopts - # ${!args_array[@]} is the list of all keys in the array (A key is 'u' in [u]=user, user is a value) + # ${!args_array[@]} is the list of all option_flags in the array (An option_flag is 'u' in [u]=user, user is a value) local getopts_parameters="" - local key="" - for key in "${!args_array[@]}" + local option_flag="" + for option_flag in "${!args_array[@]}" do - # Concatenate each keys of the array to build the string of arguments for getopts + # Concatenate each option_flags of the array to build the string of arguments for getopts # Will looks like 'abcd' for -a -b -c -d - # If the value of a key finish by =, it's an option with additionnal values. (e.g. --user bob or -u bob) - # Check the last character of the value associate to the key - if [ "${args_array[$key]: -1}" = "=" ] + # If the value of an option_flag finish by =, it's an option with additionnal values. (e.g. --user bob or -u bob) + # Check the last character of the value associate to the option_flag + if [ "${args_array[$option_flag]: -1}" = "=" ] then # For an option with additionnal values, add a ':' after the letter for getopts. - getopts_parameters="${getopts_parameters}${key}:" + getopts_parameters="${getopts_parameters}${option_flag}:" else - getopts_parameters="${getopts_parameters}${key}" + getopts_parameters="${getopts_parameters}${option_flag}" fi # Check each argument given to the function local arg="" # ${#arguments[@]} is the size of the array for arg in `seq 0 $(( ${#arguments[@]} - 1 ))` do - # And replace long option (value of the key) by the short option, the key itself + # And replace long option (value of the option_flag) by the short option, the option_flag itself # (e.g. for [u]=user, --user will be -u) # Replace long option with = - arguments[arg]="${arguments[arg]//--${args_array[$key]}/-${key} }" + arguments[arg]="${arguments[arg]//--${args_array[$option_flag]}/-${option_flag} }" # And long option without = - arguments[arg]="${arguments[arg]//--${args_array[$key]%=}/-${key}}" + arguments[arg]="${arguments[arg]//--${args_array[$option_flag]%=}/-${option_flag}}" done done @@ -132,6 +132,7 @@ ynh_handle_getopts_args () { # Declare the content of option_var as a variable. eval ${option_var}="" # Then read the array value per value + local i for i in `seq 0 $(( ${#all_args[@]} - 1 ))` do # If this argument is an option, end here. @@ -166,24 +167,27 @@ ynh_handle_getopts_args () { if [ "${arguments[0]:0:1}" != "-" ] then # If not, enter in legacy mode and manage the arguments as positionnal ones. - echo "! Helper used in legacy mode !" + ynh_print_info --message="! Helper used in legacy mode !" + local i for i in `seq 0 $(( ${#arguments[@]} -1 ))` do - # Use getopts_parameters as a list of key of the array args_array + # Try to use legacy_args as a list of option_flag of the array args_array + # Otherwise, fallback to getopts_parameters to get the option_flag. But an associative arrays isn't always sorted in the correct order... # Remove all ':' in getopts_parameters - getopts_parameters=${getopts_parameters//:} - # Get the key from getopts_parameters, by using the key according to the position of the argument. - key=${getopts_parameters:$i:1} - # Use the long option, corresponding to the key, as a variable + getopts_parameters=${legacy_args:-${getopts_parameters//:}} + # Get the option_flag from getopts_parameters, by using the option_flag according to the position of the argument. + option_flag=${getopts_parameters:$i:1} + # Use the long option, corresponding to the option_flag, as a variable # (e.g. for [u]=user, 'user' will be used as a variable) # Also, remove '=' at the end of the long option # The variable name will be stored in 'option_var' - local option_var="${args_array[$key]%=}" + local option_var="${args_array[$option_flag]%=}" # Store each value given as argument in the corresponding variable # The values will be stored in the same order than $args_array eval ${option_var}+=\"${arguments[$i]}\" done + unset legacy_args else # END LEGACY MODE # Call parse_arg and pass the modified list of args as an array of arguments. diff --git a/data/helpers.d/ip b/data/helpers.d/ip index 09c974782..c50d8be73 100644 --- a/data/helpers.d/ip +++ b/data/helpers.d/ip @@ -1,3 +1,5 @@ +#!/bin/bash + # Validate an IP address # # usage: ynh_validate_ip --family=family --ip_address=ip_address @@ -10,6 +12,7 @@ ynh_validate_ip() # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298 # Declare an array to define the options of this helper. + local legacy_args=fi declare -Ar args_array=( [f]=family= [i]=ip_address= ) local family local ip_address @@ -40,6 +43,7 @@ EOF ynh_validate_ip4() { # Declare an array to define the options of this helper. + local legacy_args=i declare -Ar args_array=( [i]=ip_address= ) local ip_address # Manage arguments with getopts @@ -59,6 +63,7 @@ ynh_validate_ip4() ynh_validate_ip6() { # Declare an array to define the options of this helper. + local legacy_args=i declare -Ar args_array=( [i]=ip_address= ) local ip_address # Manage arguments with getopts diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 4898bed92..538b00469 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -1,3 +1,5 @@ +#!/bin/bash + MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql # Open a connection as a user @@ -11,6 +13,7 @@ MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql # | arg: -d, --database - the database to connect to ynh_mysql_connect_as() { # Declare an array to define the options of this helper. + local legacy_args=upd declare -Ar args_array=( [u]=user= [p]=password= [d]=database= ) local user local password @@ -29,6 +32,7 @@ ynh_mysql_connect_as() { # | arg: -d, --database - the database to connect to ynh_mysql_execute_as_root() { # Declare an array to define the options of this helper. + local legacy_args=sd declare -Ar args_array=( [s]=sql= [d]=database= ) local sql local database @@ -47,6 +51,7 @@ ynh_mysql_execute_as_root() { # | arg: -d, --database - the database to connect to ynh_mysql_execute_file_as_root() { # Declare an array to define the options of this helper. + local legacy_args=fd declare -Ar args_array=( [f]=file= [d]=database= ) local file local database @@ -103,6 +108,7 @@ ynh_mysql_drop_db() { # | ret: the mysqldump output ynh_mysql_dump_db() { # Declare an array to define the options of this helper. + local legacy_args=d declare -Ar args_array=( [d]=database= ) local database # Manage arguments with getopts @@ -130,6 +136,7 @@ ynh_mysql_create_user() { ynh_mysql_user_exists() { # Declare an array to define the options of this helper. + local legacy_args=u declare -Ar args_array=( [u]=user= ) local user # Manage arguments with getopts @@ -164,6 +171,7 @@ ynh_mysql_drop_user() { # | arg: -p, --db_password - Password of the database. If not given, a password will be generated ynh_mysql_setup_db () { # Declare an array to define the options of this helper. + local legacy_args=unp declare -Ar args_array=( [u]=db_user= [n]=db_name= [p]=db_password= ) local db_user local db_name @@ -186,6 +194,7 @@ ynh_mysql_setup_db () { # | arg: -n, --db_name - Name of the database ynh_mysql_remove_db () { # Declare an array to define the options of this helper. + local legacy_args=un declare -Ar args_array=( [u]=db_user= [n]=db_name= ) local db_user local db_name @@ -216,6 +225,7 @@ ynh_mysql_remove_db () { # | ret: the corrected name ynh_sanitize_dbid () { # Declare an array to define the options of this helper. + local legacy_args=n declare -Ar args_array=( [n]=db_name= ) local db_name # Manage arguments with getopts diff --git a/data/helpers.d/network b/data/helpers.d/network index 0059702e9..a765d6346 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -1,3 +1,5 @@ +#!/bin/bash + # Normalize the url path syntax # Handle the slash at the beginning of path and its absence at ending # Return a normalized url path @@ -12,6 +14,7 @@ # | arg: -p, --path_url - URL path to normalize before using it ynh_normalize_url_path () { # Declare an array to define the options of this helper. + local legacy_args=p declare -Ar args_array=( [p]=path_url= ) local path_url # Manage arguments with getopts @@ -35,6 +38,7 @@ ynh_normalize_url_path () { # | arg: -p, --port - port to start to search ynh_find_port () { # Declare an array to define the options of this helper. + local legacy_args=p declare -Ar args_array=( [p]=port= ) local port # Manage arguments with getopts @@ -57,6 +61,7 @@ ynh_find_port () { # | arg: -p, --path_url - the web path to check the availability of ynh_webpath_available () { # Declare an array to define the options of this helper. + local legacy_args=dp declare -Ar args_array=( [d]=domain= [p]=path_url= ) local domain local path_url @@ -76,6 +81,7 @@ ynh_webpath_available () { # | arg: -p, --path_url - the web path to be registered ynh_webpath_register () { # Declare an array to define the options of this helper. + local legacy_args=adp declare -Ar args_array=( [a]=app= [d]=domain= [p]=path_url= ) local app local domain diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index fcba2d75a..c4332b60c 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -1,3 +1,5 @@ +#!/bin/bash + n_install_dir="/opt/node_n" node_version_path="$n_install_dir/n/versions/node" # N_PREFIX is the directory of n, it needs to be loaded as a environment variable. @@ -61,6 +63,7 @@ ynh_install_nodejs () { # Use n, https://github.com/tj/n to manage the nodejs versions # Declare an array to define the options of this helper. + local legacy_args=n declare -Ar args_array=( [n]=nodejs_version= ) local nodejs_version # Manage arguments with getopts diff --git a/data/helpers.d/package b/data/helpers.d/package index 820a61ef4..485cee957 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -1,3 +1,5 @@ +#!/bin/bash + # Check if apt is free to use, or wait, until timeout. # # [internal] @@ -29,6 +31,7 @@ ynh_wait_dpkg_free() { # | arg: -p, --package - the package name to check ynh_package_is_installed() { # Declare an array to define the options of this helper. + local legacy_args=p declare -Ar args_array=( [p]=package= ) local package # Manage arguments with getopts @@ -48,6 +51,7 @@ ynh_package_is_installed() { # | ret: the version or an empty string ynh_package_version() { # Declare an array to define the options of this helper. + local legacy_args=p declare -Ar args_array=( [p]=package= ) local package # Manage arguments with getopts diff --git a/data/helpers.d/print b/data/helpers.d/print index e605e7e4a..353fa595d 100644 --- a/data/helpers.d/print +++ b/data/helpers.d/print @@ -1,7 +1,10 @@ +#!/bin/bash + # Print a message to stderr and exit # usage: ynh_die --message=MSG [--ret_code=RETCODE] ynh_die() { # Declare an array to define the options of this helper. + local legacy_args=mc declare -Ar args_array=( [m]=message= [c]=ret_code= ) local message local ret_code @@ -17,6 +20,7 @@ ynh_die() { # usage: ynh_print_info --message="Some message" ynh_print_info() { # Declare an array to define the options of this helper. + local legacy_args=m declare -Ar args_array=( [m]=message= ) local message # Manage arguments with getopts @@ -56,6 +60,7 @@ ynh_print_log () { # | arg: -m, --message - The text to print ynh_print_warn () { # Declare an array to define the options of this helper. + local legacy_args=m declare -Ar args_array=( [m]=message= ) local message # Manage arguments with getopts @@ -70,6 +75,7 @@ ynh_print_warn () { # | arg: -m, --message - The text to print ynh_print_err () { # Declare an array to define the options of this helper. + local legacy_args=m declare -Ar args_array=( [m]=message= ) local message # Manage arguments with getopts diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 29eeecd3c..6f75f6c80 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -1,3 +1,5 @@ +#!/bin/bash + # Get an application setting # # usage: ynh_app_setting_get --app=app --key=key @@ -5,6 +7,7 @@ # | arg: -k, --key - the setting to get ynh_app_setting_get() { # Declare an array to define the options of this helper. + local legacy_args=ak declare -Ar args_array=( [a]=app= [k]=key= ) local app local key @@ -22,6 +25,7 @@ ynh_app_setting_get() { # | arg: -v, --value - the setting value to set ynh_app_setting_set() { # Declare an array to define the options of this helper. + local legacy_args=akv declare -Ar args_array=( [a]=app= [k]=key= [v]=value= ) local app local key @@ -39,6 +43,7 @@ ynh_app_setting_set() { # | arg: -k, --key - the setting to delete ynh_app_setting_delete() { # Declare an array to define the options of this helper. + local legacy_args=ak declare -Ar args_array=( [a]=app= [k]=key= ) local app local key diff --git a/data/helpers.d/string b/data/helpers.d/string index b4dfb596d..739757d43 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -1,3 +1,5 @@ +#!/bin/bash + # Generate a random string # # example: pwd=$(ynh_string_random --length=8) @@ -6,6 +8,7 @@ # | arg: -l, --length - the string length to generate (default: 24) ynh_string_random() { # Declare an array to define the options of this helper. + local legacy_args=l declare -Ar args_array=( [l]=length= ) local length # Manage arguments with getopts @@ -29,6 +32,7 @@ ynh_string_random() { # (see sed manual page for more information) ynh_replace_string () { # Declare an array to define the options of this helper. + local legacy_args=mrf declare -Ar args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) local match_string local replace_string @@ -55,6 +59,7 @@ ynh_replace_string () { # characters, you can't use some regular expressions and sub-expressions. ynh_replace_special_string () { # Declare an array to define the options of this helper. + local legacy_args=mrf declare -Ar args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= ) local match_string local replace_string diff --git a/data/helpers.d/system b/data/helpers.d/system index 70cc57493..968135f16 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -1,3 +1,5 @@ +#!/bin/bash + # Manage a fail of the script # # [internal] diff --git a/data/helpers.d/user b/data/helpers.d/user index 3947ebe7f..1fa8f3f79 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -1,3 +1,5 @@ +#!/bin/bash + # Check if a YunoHost user exists # # example: ynh_user_exists 'toto' || exit 1 @@ -6,6 +8,7 @@ # | arg: -u, --username - the username to check ynh_user_exists() { # Declare an array to define the options of this helper. + local legacy_args=u declare -Ar args_array=( [u]=username= ) local username # Manage arguments with getopts @@ -24,6 +27,7 @@ ynh_user_exists() { # | ret: string - the key's value ynh_user_get_info() { # Declare an array to define the options of this helper. + local legacy_args=uk declare -Ar args_array=( [u]=username= [k]=key= ) local username local key @@ -50,6 +54,7 @@ ynh_user_list() { # | arg: -u, --username - the username to check ynh_system_user_exists() { # Declare an array to define the options of this helper. + local legacy_args=u declare -Ar args_array=( [u]=username= ) local username # Manage arguments with getopts @@ -65,6 +70,7 @@ ynh_system_user_exists() { # | arg: -h, --home_dir - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home ynh_system_user_create () { # Declare an array to define the options of this helper. + local legacy_args=uh declare -Ar args_array=( [u]=username= [h]=home_dir= ) local username local home_dir @@ -88,6 +94,7 @@ ynh_system_user_create () { # | arg: -u, --username - Name of the system user that will be create ynh_system_user_delete () { # Declare an array to define the options of this helper. + local legacy_args=u declare -Ar args_array=( [u]=username= ) local username # Manage arguments with getopts diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 1488289fa..60ba709ba 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -1,3 +1,5 @@ +#!/bin/bash + # Extract a key from a plain command output # # example: yunohost user info tata --output-as plain | ynh_get_plain_key mail @@ -146,6 +148,7 @@ ynh_backup_before_upgrade () { # | arg: -s, --source_id - Name of the app, if the package contains more than one app ynh_setup_source () { # Declare an array to define the options of this helper. + local legacy_args=ds declare -Ar args_array=( [d]=dest_dir= [s]=source_id= ) local dest_dir local source_id From 7de184a520b9f746f2fe4f8eb944e5438f5bb223 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 4 Jan 2019 17:21:17 +0100 Subject: [PATCH 054/721] Use getopts helpers in mysql, again --- data/helpers.d/mysql | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 538b00469..9b4908097 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -40,8 +40,8 @@ ynh_mysql_execute_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ - "$database" <<< "$sql" + ynh_mysql_connect_as --user="root" --password="$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ + --database="$database" <<< "$sql" } # Execute a command from a file as root user @@ -59,8 +59,8 @@ ynh_mysql_execute_file_as_root() { ynh_handle_getopts_args "$@" database="${database:-}" - ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ - "$database" < "$file" + ynh_mysql_connect_as --user="root" --password="$(sudo cat $MYSQL_ROOT_PWD_FILE)" \ + --database="$database" < "$file" } # Create a database and grant optionnaly privilegies to a user @@ -83,7 +83,7 @@ ynh_mysql_create_db() { sql+=" WITH GRANT OPTION;" fi - ynh_mysql_execute_as_root "$sql" + ynh_mysql_execute_as_root --sql="$sql" } # Drop a database @@ -96,7 +96,7 @@ ynh_mysql_create_db() { # usage: ynh_mysql_drop_db db # | arg: db - the database name to drop ynh_mysql_drop_db() { - ynh_mysql_execute_as_root "DROP DATABASE ${1};" + ynh_mysql_execute_as_root --sql="DROP DATABASE ${1};" } # Dump a database @@ -126,7 +126,7 @@ ynh_mysql_dump_db() { # | arg: pwd - the password to identify user by ynh_mysql_create_user() { ynh_mysql_execute_as_root \ - "CREATE USER '${1}'@'localhost' IDENTIFIED BY '${2}';" + --sql="CREATE USER '${1}'@'localhost' IDENTIFIED BY '${2}';" } # Check if a mysql user exists @@ -142,7 +142,7 @@ ynh_mysql_user_exists() # Manage arguments with getopts ynh_handle_getopts_args "$@" - if [[ -z $(ynh_mysql_execute_as_root "SELECT User from mysql.user WHERE User = '$user';") ]] + if [[ -z $(ynh_mysql_execute_as_root --sql="SELECT User from mysql.user WHERE User = '$user';") ]] then return 1 else @@ -157,7 +157,7 @@ ynh_mysql_user_exists() # usage: ynh_mysql_drop_user user # | arg: user - the user name to drop ynh_mysql_drop_user() { - ynh_mysql_execute_as_root "DROP USER '${1}'@'localhost';" + ynh_mysql_execute_as_root --sql="DROP USER '${1}'@'localhost';" } # Create a database, an user and its password. Then store the password in the app's config @@ -210,7 +210,7 @@ ynh_mysql_remove_db () { fi # Remove mysql user if it exists - if $(ynh_mysql_user_exists $db_user); then + if $(ynh_mysql_user_exists --user=$db_user); then ynh_mysql_drop_user $db_user fi } From 1a4e661e4eeadad45faab114ed8a9ff66e8b82fc Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 4 Jan 2019 18:43:56 +0100 Subject: [PATCH 055/721] Keep db_pwd instead of db_password --- data/helpers.d/mysql | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 9b4908097..1fa01224a 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -165,26 +165,26 @@ ynh_mysql_drop_user() { # After executing this helper, the password of the created database will be available in $db_pwd # It will also be stored as "mysqlpwd" into the app settings. # -# usage: ynh_mysql_setup_db --db_user=user --db_name=name [--db_password=pwd] +# usage: ynh_mysql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] # | arg: -u, --db_user - Owner of the database # | arg: -n, --db_name - Name of the database -# | arg: -p, --db_password - Password of the database. If not given, a password will be generated +# | arg: -p, --db_pwd - Password of the database. If not given, a password will be generated ynh_mysql_setup_db () { # Declare an array to define the options of this helper. local legacy_args=unp - declare -Ar args_array=( [u]=db_user= [n]=db_name= [p]=db_password= ) + declare -Ar args_array=( [u]=db_user= [n]=db_name= [p]=db_pwd= ) local db_user local db_name - local db_password + local db_pwd # Manage arguments with getopts ynh_handle_getopts_args "$@" local new_db_pwd=$(ynh_string_random) # Generate a random password - # If $db_password is not given, use new_db_pwd instead for db_password. - db_password="${db_password:-$new_db_pwd}" + # If $db_pwd is not given, use new_db_pwd instead for db_pwd + db_pwd="${db_pwd:-$new_db_pwd}" - ynh_mysql_create_db "$db_name" "$db_user" "$db_password" # Create the database - ynh_app_setting_set --app=$app --key=mysqlpwd --value=$db_password # Store the password in the app's config + ynh_mysql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database + ynh_app_setting_set --app=$app --key=mysqlpwd --value=$db_pwd # Store the password in the app's config } # Remove a database if it exists, and the associated user From 50f3291ea7eebb7edc229229f45ce4a5cf66fd1c Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 4 Jan 2019 18:44:40 +0100 Subject: [PATCH 056/721] Fix getopts with blank argument's value There's no reason in that loop where we're looking for values of an option to have a blank cell in the array of arguments. Unless for an option with a missing value. In that case, it's better to ignore this condition and store a blank value in the variable. Otherwise, in case of missing value for an option, with this condition, we enter in an infinite loop, because the shift will stay at 0. --- data/helpers.d/getopts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index e9fbe18e8..b3d444767 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -136,7 +136,7 @@ ynh_handle_getopts_args () { for i in `seq 0 $(( ${#all_args[@]} - 1 ))` do # If this argument is an option, end here. - if [ "${all_args[$i]:0:1}" == "-" ] || [ -z "${all_args[$i]}" ] + if [ "${all_args[$i]:0:1}" == "-" ] then # Ignore the first value of the array, which is the option itself if [ "$i" -ne 0 ]; then From 540291a7e072dde610d80e45ec70496d9e2e58e6 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 4 Jan 2019 19:26:54 +0100 Subject: [PATCH 057/721] Ignore useless parameters in legacy mode --- data/helpers.d/getopts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index b3d444767..efaa8d065 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -166,8 +166,9 @@ ynh_handle_getopts_args () { # Check if there's getopts arguments if [ "${arguments[0]:0:1}" != "-" ] then - # If not, enter in legacy mode and manage the arguments as positionnal ones. - ynh_print_info --message="! Helper used in legacy mode !" + # If not, enter in legacy mode and manage the arguments as positionnal ones.. + # Dot not echo, to prevent to go through a helper output. But print only in the log. + set -x; echo "! Helper used in legacy mode !" > /dev/null; set +x local i for i in `seq 0 $(( ${#arguments[@]} -1 ))` do @@ -177,6 +178,10 @@ ynh_handle_getopts_args () { getopts_parameters=${legacy_args:-${getopts_parameters//:}} # Get the option_flag from getopts_parameters, by using the option_flag according to the position of the argument. option_flag=${getopts_parameters:$i:1} + if [ -z "$option_flag" ]; then + ynh_print_warn --message="Too many arguments ! \"${arguments[$i]}\" will be ignored." + continue + fi # Use the long option, corresponding to the option_flag, as a variable # (e.g. for [u]=user, 'user' will be used as a variable) # Also, remove '=' at the end of the long option From bf443f2ec0fc270bff4730200de2921152d8b04b Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 4 Jan 2019 19:30:07 +0100 Subject: [PATCH 058/721] db_pwd shouldn't be local --- data/helpers.d/mysql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 1fa01224a..fa1a61dab 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -175,7 +175,7 @@ ynh_mysql_setup_db () { declare -Ar args_array=( [u]=db_user= [n]=db_name= [p]=db_pwd= ) local db_user local db_name - local db_pwd + db_pwd="" # Manage arguments with getopts ynh_handle_getopts_args "$@" From d1a822211ce2f9adf3f3adf217b4c2d46734260f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Bourr=C3=A9?= Date: Sat, 5 Jan 2019 03:11:08 +0100 Subject: [PATCH 059/721] add restart and reload functionality to yunohost service --- data/actionsmap/yunohost.yml | 27 +++++++++++++++++ src/yunohost/service.py | 58 ++++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 08478e2b4..9c540ed1f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1178,6 +1178,33 @@ service: nargs: "+" metavar: NAME + ### service_reload() + reload: + action_help: Reload one or more services + arguments: + names: + help: Service name to reload + nargs: "+" + metavar: NAME + + ### service_restart() + restart: + action_help: Restart one or more services. If the services are not running yet, they will be started. + arguments: + names: + help: Service name to restart + nargs: "+" + metavar: NAME + + ### service_reload_or_restart() + reload_or_restart: + action_help: Reload one or more services if they support it. If not, restart them instead. If the services are not running yet, they will be started. + arguments: + names: + help: Service name to reload or restart + nargs: "+" + metavar: NAME + ### service_enable() enable: action_help: Enable one or more services diff --git a/src/yunohost/service.py b/src/yunohost/service.py index a8ef0e796..286f272b7 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -152,6 +152,60 @@ def service_stop(names): logger.debug(m18n.n('service_already_stopped', service=name)) +def service_reload(names): + """ + Reload one or more services + + Keyword argument: + name -- Services name to reload + + """ + if isinstance(names, str): + names = [names] + for name in names: + if _run_service_command('reload', name): + logger.success(m18n.n('service_reloaded', service=name)) + else: + if service_status(name)['status'] != 'inactive': + raise YunohostError('service_reload_failed', service=name, logs=_get_journalctl_logs(name)) + + +def service_restart(names): + """ + Restart one or more services. If the services are not running yet, they will be started. + + Keyword argument: + name -- Services name to restart + + """ + if isinstance(names, str): + names = [names] + for name in names: + if _run_service_command('restart', name): + logger.success(m18n.n('service_restarted', service=name)) + else: + if service_status(name)['status'] != 'inactive': + raise YunohostError('service_restart_failed', service=name, logs=_get_journalctl_logs(name)) + + +def service_reload_or_restart(names): + """ + Reload one or more services if they support it. If not, restart them instead. If the services are not running yet, they will be started. + + Keyword argument: + name -- Services name to reload or restart + + """ + if isinstance(names, str): + names = [names] + for name in names: + if _run_service_command('reload-or-restart', name): + logger.success(m18n.n('service_reloaded_or_restarted', service=name)) + else: + if service_status(name)['status'] != 'inactive': + raise YunohostError('service_reloaded_or_restarted_failed', service=name, logs=_get_journalctl_logs(name)) + + @is_unit_operation() def service_enable(operation_logger, names): """ @@ -597,14 +651,14 @@ def _run_service_command(action, service): if service not in services.keys(): raise YunohostError('service_unknown', service=service) - possible_actions = ['start', 'stop', 'restart', 'reload', 'enable', 'disable'] + possible_actions = ['start', 'stop', 'restart', 'reload', 'reload-or-restart', 'enable', 'disable'] if action not in possible_actions: raise ValueError("Unknown action '%s', available actions are: %s" % (action, ", ".join(possible_actions))) cmd = 'systemctl %s %s' % (action, service) need_lock = services[service].get('need_lock', False) \ - and action in ['start', 'stop', 'restart', 'reload'] + and action in ['start', 'stop', 'restart', 'reload', 'reload-or-restart'] try: # Launch the command From 0a3e5c60ead612c921f3a31aadfa71e32359ca7a Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 5 Jan 2019 15:09:17 +0100 Subject: [PATCH 060/721] source getopts in filesystem Many core backup script source /usr/share/yunohost/helpers.d/filesystem only. Not sure it's a wonderful idea... --- data/helpers.d/filesystem | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 2e9f93166..0f2e06b64 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -1,5 +1,7 @@ #!/bin/bash +source /usr/share/yunohost/helpers.d/getopts + CAN_BIND=${CAN_BIND:-1} # Add a file or a directory to the list of paths to backup From bbfa9fc124eefc1a445dda5ec92f7ca9d77ea523 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 7 Jan 2019 18:26:38 +0100 Subject: [PATCH 061/721] Fix several issues with bootprompt --- bin/yunoprompt | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/bin/yunoprompt b/bin/yunoprompt index 2b2a6cfb2..09400639b 100755 --- a/bin/yunoprompt +++ b/bin/yunoprompt @@ -1,8 +1,5 @@ #!/bin/bash -# Fetch ips -ip=$(hostname --all-ip-address) - # Fetch SSH fingerprints i=0 for key in $(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key.pub 2> /dev/null) ; do @@ -32,11 +29,17 @@ EOF # Build the actual message # +sleep 5 +# Get local IP +# (we do this after the sleep 5 to have +# better chances that the network is up) +local_ip=$(hostname --all-ip-address | awk '{print $1}') + LOGO_AND_FINGERPRINTS=$(cat << EOF $LOGO - IP: ${ip} + IP: ${local_ip} SSH fingerprints: ${fingerprint[0]} ${fingerprint[1]} @@ -51,17 +54,35 @@ if [[ -f /etc/yunohost/installed ]] then echo "$LOGO_AND_FINGERPRINTS" > /etc/issue else - sleep 5 chvt 2 + + # Formatting + [[ -n "$local_ip" ]] && local_ip=$(echo -e "https://$local_ip/") || local_ip="(no ip detected?)" + echo "$LOGO_AND_FINGERPRINTS" - echo -e "\e[m Post-installation \e[0m" - echo "Congratulations! YunoHost has been successfully installed.\nTwo more steps are required to activate the services of your server." - read -p "Proceed to post-installation? (y/n)\nAlternatively, you can proceed the post-installation on https://${ip}" -n 1 + cat << EOF +=============================================================================== +You should now proceed with Yunohost post-installation. This is where you will +be asked for : + - the main domain of your server ; + - the administration password. + +You can perform this step : + - from your web browser, by accessing : ${local_ip} + - or in this terminal by answering 'yes' to the following question + +If this is your first time with YunoHost, it is strongly recommended to take +time to read the administator documentation and in particular the sections +'Finalizing your setup' and 'Getting to know YunoHost'. It is available at +the following URL : https://yunohost.org/admindoc +=============================================================================== +EOF + + read -p "Proceed with post-installation? (y/n) " RESULT=1 while [ $RESULT -gt 0 ]; do if [[ $REPLY =~ ^[Nn]$ ]]; then - chvt 1 - exit 0 + break fi echo -e "\n" /usr/bin/yunohost tools postinstall @@ -71,4 +92,6 @@ else read -p "Retry? (y/n) " -n 1 fi done + chvt 1 + exit 0 fi From 9e1cd734001c0fd6a7c18e95f08041c67826502b Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 7 Jan 2019 18:51:12 +0100 Subject: [PATCH 062/721] [mod] add more verbose error messages --- locales/en.json | 2 +- src/yunohost/app.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6e4fda2ac..0f8b3b861 100644 --- a/locales/en.json +++ b/locales/en.json @@ -455,7 +455,7 @@ "system_upgraded": "The system has been upgraded", "system_username_exists": "Username already exists in the system users", "unbackup_app": "App '{app:s}' will not be saved", - "unexpected_error": "An unexpected error occured", + "unexpected_error": "An unexpected error occured: {error}", "unit_unknown": "Unknown unit '{unit:s}'", "unlimit": "No quota", "unrestore_app": "App '{app:s}' will not be restored", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 11f92afd1..9d1d8b447 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -830,11 +830,12 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on ) except (KeyboardInterrupt, EOFError): install_retcode = -1 - except: - logger.exception(m18n.n('unexpected_error')) + except Exception: + import traceback + logger.exception(m18n.n('unexpected_error', traceback=u"\n" + traceback.format_exc())) finally: if install_retcode != 0: - error_msg = operation_logger.error(m18n.n('unexpected_error')) + error_msg = operation_logger.error(m18n.n('unexpected_error', traceback='shell command return code: %s' % install_retcode)) if not no_remove_on_failure: # Setup environment for remove script env_dict_remove = {} From 2e0efb5b5629afad07dd1f994cc42c969624409d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 7 Jan 2019 18:59:23 +0100 Subject: [PATCH 063/721] [fix] bad key for string formatting --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 9d1d8b447..32d3012cb 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -832,10 +832,10 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on install_retcode = -1 except Exception: import traceback - logger.exception(m18n.n('unexpected_error', traceback=u"\n" + traceback.format_exc())) + logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) finally: if install_retcode != 0: - error_msg = operation_logger.error(m18n.n('unexpected_error', traceback='shell command return code: %s' % install_retcode)) + error_msg = operation_logger.error(m18n.n('unexpected_error', error='shell command return code: %s' % install_retcode)) if not no_remove_on_failure: # Setup environment for remove script env_dict_remove = {} From 7362502ad13359c85a36f3e5243f07a9589d792b Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 7 Jan 2019 19:05:55 +0100 Subject: [PATCH 064/721] [fix] propagate --no-checks cert-install option to renew crontab --- src/yunohost/certificate.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index aea0c60b1..855910b8a 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -286,7 +286,7 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F _configure_for_acme_challenge(auth, domain) _fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks) - _install_cron() + _install_cron(no_checks=no_checks) logger.success( m18n.n("certmanager_cert_install_success", domain=domain)) @@ -407,12 +407,27 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal # -def _install_cron(): +def _install_cron(no_checks=False): cron_job_file = "/etc/cron.daily/yunohost-certificate-renew" + # we need to check if "--no-checks" isn't already put inside the existing + # crontab, if it's the case it's probably because another domain needed it + # at some point so we keep it + if not no_checks and os.path.exists(cron_job_file): + with open(cron_job_file, "r") as f: + # no the best test in the world but except if we uses a shell + # script parser I'm not expected a much more better way to do that + no_checks = "--no-checks" in f.read() + + command = "yunohost domain cert-renew --email\n" + + if no_checks: + # handle trailing "\n with ":-1" + command = command[:-1] + " --no-checks\n" + with open(cron_job_file, "w") as f: f.write("#!/bin/bash\n") - f.write("yunohost domain cert-renew --email\n") + f.write(command) _set_permissions(cron_job_file, "root", "root", 0o755) From f266cceb265559e8bf3d272b1b4837bb2206c08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 7 Jan 2019 20:23:41 +0100 Subject: [PATCH 065/721] Fix typo --- src/yunohost/domain.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 280f8a9c3..088f7724f 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -4,8 +4,7 @@ Copyright (C) 2013 YunoHost - This progra - m is free software; you can redistribute it and/or modify + This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. From 4935aebb16a144cca3ee79dd1c027979fe9ecdc1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 8 Jan 2019 16:25:43 +0100 Subject: [PATCH 066/721] Revert "[enh] Improve upnp support (#542)" This reverts commit 640bc494cb11937bfb2f3901db203c7b84e449c8. --- src/yunohost/firewall.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index 39102bdc2..1c44efe99 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -342,8 +342,7 @@ def firewall_upnp(action='status', no_refresh=False): # Refresh port mapping using UPnP if not no_refresh: upnpc = miniupnpc.UPnP() - upnpc.discoverdelay = 62000 - upnpc.localport = 1900 + upnpc.discoverdelay = 3000 # Discover UPnP device(s) logger.debug('discovering UPnP devices...') From 3990527f96d4b49a57c3f1b475a2969b0e768ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Bourr=C3=A9?= Date: Wed, 9 Jan 2019 00:40:26 +0100 Subject: [PATCH 067/721] add locales --- locales/en.json | 7 ++++++- src/yunohost/service.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6e4fda2ac..848b3bc69 100644 --- a/locales/en.json +++ b/locales/en.json @@ -220,7 +220,6 @@ "log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log display {name}'", "log_link_to_failed_log": "The operation '{desc}' has failed ! To get help, please provide the full log of this operation by clicking here", "log_help_to_get_failed_log": "The operation '{desc}' has failed ! To get help, please share the full log of this operation using the command 'yunohost log display {name} --share'", - "log_category_404": "The log category '{category}' does not exist", "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list to see all available operation logs'", "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", "log_app_addaccess": "Add access to '{}'", @@ -442,6 +441,12 @@ "service_regenconf_pending_applying": "Applying pending configuration for service '{service}'...", "service_remove_failed": "Unable to remove service '{service:s}'", "service_removed": "The service '{service:s}' has been removed", + "service_reload_failed": "Unable to reload service '{service:s}'\n\nRecent service logs:{logs:s}", + "service_reloaded": "The service '{service:s}' has been reloaded", + "service_restart_failed": "Unable to restart service '{service:s}'\n\nRecent service logs:{logs:s}", + "service_restarted": "The service '{service:s}' has been restarted", + "service_reload_or_restart_failed": "Unable to reload or restart service '{service:s}'\n\nRecent service logs:{logs:s}", + "service_reloaded_or_restarted": "The service '{service:s}' has been reloaded or restarted", "service_start_failed": "Unable to start service '{service:s}'\n\nRecent service logs:{logs:s}", "service_started": "The service '{service:s}' has been started", "service_status_failed": "Unable to determine status of service '{service:s}'", diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 286f272b7..d9bd39fba 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -203,7 +203,7 @@ def service_reload_or_restart(names): logger.success(m18n.n('service_reloaded_or_restarted', service=name)) else: if service_status(name)['status'] != 'inactive': - raise YunohostError('service_reloaded_or_restarted_failed', service=name, logs=_get_journalctl_logs(name)) + raise YunohostError('service_reload_or_restart_failed', service=name, logs=_get_journalctl_logs(name)) @is_unit_operation() From 46f5592eb5fc39c1d08d0e701f0f6a12f0f44d81 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Thu, 10 Jan 2019 07:13:43 +0100 Subject: [PATCH 068/721] Tiny typographic changes --- locales/en.json | 108 ++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/locales/en.json b/locales/en.json index 0f8b3b861..4c0387695 100644 --- a/locales/en.json +++ b/locales/en.json @@ -30,20 +30,20 @@ "app_not_properly_removed": "{app:s} has not been properly removed", "app_package_need_update": "The app {app} package needs to be updated to follow YunoHost changes", "app_removed": "{app:s} has been removed", - "app_requirements_checking": "Checking required packages for {app}...", + "app_requirements_checking": "Checking required packages for {app}…", "app_requirements_failed": "Unable to meet requirements for {app}: {error}", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", "app_sources_fetch_failed": "Unable to fetch sources files", "app_unknown": "Unknown app", "app_unsupported_remote_type": "Unsupported remote type used for the app", - "app_upgrade_app_name": "Upgrading app {app}...", + "app_upgrade_app_name": "Upgrading app {app}…", "app_upgrade_failed": "Unable to upgrade {app:s}", "app_upgrade_some_app_failed": "Unable to upgrade some applications", "app_upgraded": "{app:s} has been upgraded", "appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.", - "appslist_could_not_migrate": "Could not migrate app list {appslist:s} ! Unable to parse the url... The old cron job has been kept in {bkp_file:s}.", + "appslist_could_not_migrate": "Could not migrate app list {appslist:s}! Unable to parse the url… The old cron job has been kept in {bkp_file:s}.", "appslist_fetched": "The application list {appslist:s} has been fetched", - "appslist_migrating": "Migrating application list {appslist:s} ...", + "appslist_migrating": "Migrating application list {appslist:s}…", "appslist_name_already_tracked": "There is already a registered application list with name {name:s}.", "appslist_removed": "The application list {appslist:s} has been removed", "appslist_retrieve_bad_format": "Retrieved file for application list {appslist:s} is not valid", @@ -62,10 +62,10 @@ "backup_abstract_method": "This backup method hasn't yet been implemented", "backup_action_required": "You must specify something to save", "backup_app_failed": "Unable to back up the app '{app:s}'", - "backup_applying_method_borg": "Sending all files to backup into borg-backup repository...", - "backup_applying_method_copy": "Copying all files to backup...", - "backup_applying_method_custom": "Calling the custom backup method '{method:s}'...", - "backup_applying_method_tar": "Creating the backup tar archive...", + "backup_applying_method_borg": "Sending all files to backup into borg-backup repository…", + "backup_applying_method_copy": "Copying all files to backup…", + "backup_applying_method_custom": "Calling the custom backup method '{method:s}'…", + "backup_applying_method_tar": "Creating the backup tar archive…", "backup_archive_app_not_found": "App '{app:s}' not found in the backup archive", "backup_archive_broken_link": "Unable to access backup archive (broken link to {path:s})", "backup_archive_mount_failed": "Mounting the backup archive failed", @@ -81,7 +81,7 @@ "backup_copying_to_organize_the_archive": "Copying {size:s}MB to organize the archive", "backup_couldnt_bind": "Couldn't bind {src:s} to {dest:s}.", "backup_created": "Backup created", - "backup_creating_archive": "Creating the backup archive...", + "backup_creating_archive": "Creating the backup archive…", "backup_creation_failed": "Backup creation failed", "backup_csv_addition_failed": "Unable to add files to backup into the CSV file", "backup_csv_creation_failed": "Unable to create the CSV file needed for future restore operations", @@ -90,7 +90,7 @@ "backup_custom_need_mount_error": "Custom backup method failure on 'need_mount' step", "backup_delete_error": "Unable to delete '{path:s}'", "backup_deleted": "The backup has been deleted", - "backup_extracting_archive": "Extracting the backup archive...", + "backup_extracting_archive": "Extracting the backup archive…", "backup_hook_unknown": "Backup hook '{hook:s}' unknown", "backup_invalid_archive": "Invalid backup archive", "backup_method_borg_finished": "Backup into borg finished", @@ -104,8 +104,8 @@ "backup_output_directory_required": "You must provide an output directory for the backup", "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.", "backup_php5_to_php7_migration_may_fail": "Could not convert your archive to support php7, your php apps may fail to restore (reason: {error:s})", - "backup_running_app_script": "Running backup script of app '{app:s}'...", - "backup_running_hooks": "Running backup hooks...", + "backup_running_app_script": "Running backup script of app '{app:s}'…", + "backup_running_hooks": "Running backup hooks…", "backup_system_part_failed": "Unable to backup the '{part:s}' system part", "backup_unable_to_organize_files": "Unable to organize files in the archive with the quick method", "backup_with_no_backup_script_for_app": "App {app:s} has no backup script. Ignoring.", @@ -119,7 +119,7 @@ "certmanager_cert_install_success_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!", "certmanager_cert_renew_success": "Successfully renewed Let's Encrypt certificate for domain {domain:s}!", "certmanager_cert_signing_failed": "Signing the new certificate failed", - "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow...", + "certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow…", "certmanager_conflicting_nginx_file": "Unable to prepare domain for ACME challenge: the nginx configuration file {filepath:s} is conflicting and should be removed first", "certmanager_couldnt_fetch_intermediate_cert": "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted - please try again later.", "certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use --force)", @@ -164,23 +164,23 @@ "domain_zone_not_found": "DNS zone file not found for domain {:s}", "domains_available": "Available domains:", "done": "Done", - "downloading": "Downloading...", + "downloading": "Downloading…", "dyndns_could_not_check_provide": "Could not check if {provider:s} can provide {domain:s}.", "dyndns_cron_installed": "The DynDNS cron job has been installed", "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job", "dyndns_cron_removed": "The DynDNS cron job has been removed", "dyndns_ip_update_failed": "Unable to update IP address on DynDNS", "dyndns_ip_updated": "Your IP address has been updated on DynDNS", - "dyndns_key_generating": "DNS key is being generated, it may take a while...", + "dyndns_key_generating": "DNS key is being generated, it may take a while…", "dyndns_key_not_found": "DNS key not found for the domain", "dyndns_no_domain_registered": "No domain has been registered with DynDNS", "dyndns_registered": "The DynDNS domain has been registered", "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", "dyndns_unavailable": "Domain {domain:s} is not available.", - "executing_command": "Executing command '{command:s}'...", - "executing_script": "Executing script '{script:s}'...", - "extracting": "Extracting...", + "executing_command": "Executing command '{command:s}'…", + "executing_script": "Executing script '{script:s}'…", + "extracting": "Extracting…", "experimental_feature": "Warning: this feature is experimental and not consider stable, you shouldn't be using it except if you know what you are doing.", "field_invalid": "Invalid field '{:s}'", "firewall_reload_failed": "Unable to reload the firewall", @@ -214,12 +214,12 @@ "invalid_url_format": "Invalid URL format", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", - "log_corrupted_md_file": "The yaml metadata file associated with logs is corrupted : '{md_file}'", + "log_corrupted_md_file": "The yaml metadata file associated with logs is corrupted: '{md_file}'", "log_category_404": "The log category '{category}' does not exist", "log_link_to_log": "Full log of this operation: '{desc}'", "log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log display {name}'", - "log_link_to_failed_log": "The operation '{desc}' has failed ! To get help, please provide the full log of this operation by clicking here", - "log_help_to_get_failed_log": "The operation '{desc}' has failed ! To get help, please share the full log of this operation using the command 'yunohost log display {name} --share'", + "log_link_to_failed_log": "The operation '{desc}' has failed! To get help, please provide the full log of this operation by clicking here", + "log_help_to_get_failed_log": "The operation '{desc}' has failed! To get help, please share the full log of this operation using the command 'yunohost log display {name} --share'", "log_category_404": "The log category '{category}' does not exist", "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list to see all available operation logs'", "log_operation_unit_unclosed_properly": "Operation unit has not been closed properly", @@ -270,11 +270,11 @@ "migrate_tsig_end": "Migration to hmac-sha512 finished", "migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}", "migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512", - "migrate_tsig_wait": "Let's wait 3min for the dyndns server to take the new key into account...", - "migrate_tsig_wait_2": "2min...", - "migrate_tsig_wait_3": "1min...", - "migrate_tsig_wait_4": "30 secondes...", - "migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed !", + "migrate_tsig_wait": "Let's wait 3min for the dyndns server to take the new key into account…", + "migrate_tsig_wait_2": "2min…", + "migrate_tsig_wait_3": "1min…", + "migrate_tsig_wait_4": "30 secondes…", + "migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed!", "migration_description_0001_change_cert_group_to_sslcert": "Change certificates group permissions from 'metronome' to 'ssl-cert'", "migration_description_0002_migrate_to_tsig_sha256": "Improve security of dyndns TSIG by using SHA512 instead of MD5", "migration_description_0003_migrate_to_stretch": "Upgrade the system to Debian Stretch and YunoHost 3.0", @@ -285,29 +285,29 @@ "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", "migration_0003_backward_impossible": "The stretch migration cannot be reverted.", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", - "migration_0003_patching_sources_list": "Patching the sources.lists ...", - "migration_0003_main_upgrade": "Starting main upgrade ...", - "migration_0003_fail2ban_upgrade": "Starting the fail2ban upgrade ...", - "migration_0003_restoring_origin_nginx_conf": "Your file /etc/nginx/nginx.conf was edited somehow. The migration is going to reset back to its original state first... The previous file will be available as {backup_dest}.", - "migration_0003_yunohost_upgrade": "Starting the yunohost package upgrade ... The migration will end, but the actual upgrade will happen right after. After the operation is complete, you might have to re-log on the webadmin.", - "migration_0003_not_jessie": "The current debian distribution is not Jessie !", + "migration_0003_patching_sources_list": "Patching the sources.lists…", + "migration_0003_main_upgrade": "Starting main upgrade…", + "migration_0003_fail2ban_upgrade": "Starting the fail2ban upgrade…", + "migration_0003_restoring_origin_nginx_conf": "Your file /etc/nginx/nginx.conf was edited somehow. The migration is going to reset back to its original state first… The previous file will be available as {backup_dest}.", + "migration_0003_yunohost_upgrade": "Starting the yunohost package upgrade… The migration will end, but the actual upgrade will happen right after. After the operation is complete, you might have to re-log on the webadmin.", + "migration_0003_not_jessie": "The current debian distribution is not Jessie!", "migration_0003_system_not_fully_up_to_date": "Your system is not fully up to date. Please perform a regular upgrade before running the migration to stretch.", - "migration_0003_still_on_jessie_after_main_upgrade": "Something wrong happened during the main upgrade : system is still on Jessie !? To investigate the issue, please look at {log} :s ...", - "migration_0003_general_warning": "Please note that this migration is a delicate operation. While the YunoHost team did its best to review and test it, the migration might still break parts of the system or apps.\n\nTherefore, we recommend you to :\n - Perform a backup of any critical data or app. More infos on https://yunohost.org/backup ;\n - Be patient after launching the migration : depending on your internet connection and hardware, it might take up to a few hours for everything to upgrade.\n\nAdditionally, the port for SMTP, used by external email clients (like Thunderbird or K9-Mail) was changed from 465 (SSL/TLS) to 587 (STARTTLS). The old port 465 will automatically be closed and the new port 587 will be opened in the firewall. You and your users *will* have to adapt the configuration of your email clients accordingly!", - "migration_0003_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from an applist or are not flagged as 'working'. Consequently, we cannot guarantee that they will still work after the upgrade : {problematic_apps}", - "migration_0003_modified_files": "Please note that the following files were found to be manually modified and might be overwritten at the end of the upgrade : {manually_modified_files}", + "migration_0003_still_on_jessie_after_main_upgrade": "Something wrong happened during the main upgrade: system is still on Jessie!? To investigate the issue, please look at {log}:s…", + "migration_0003_general_warning": "Please note that this migration is a delicate operation. While the YunoHost team did its best to review and test it, the migration might still break parts of the system or apps.\n\nTherefore, we recommend you to:\n - Perform a backup of any critical data or app. More infos on https://yunohost.org/backup;\n - Be patient after launching the migration: depending on your internet connection and hardware, it might take up to a few hours for everything to upgrade.\n\nAdditionally, the port for SMTP, used by external email clients (like Thunderbird or K9-Mail) was changed from 465 (SSL/TLS) to 587 (STARTTLS). The old port 465 will automatically be closed and the new port 587 will be opened in the firewall. You and your users *will* have to adapt the configuration of your email clients accordingly!", + "migration_0003_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from an applist or are not flagged as 'working'. Consequently, we cannot guarantee that they will still work after the upgrade: {problematic_apps}", + "migration_0003_modified_files": "Please note that the following files were found to be manually modified and might be overwritten at the end of the upgrade: {manually_modified_files}", "migration_0005_postgresql_94_not_installed": "Postgresql was not installed on your system. Nothing to do!", - "migration_0005_postgresql_96_not_installed": "Postgresql 9.4 has been found to be installed, but not postgresql 9.6 !? Something weird might have happened on your system :( ...", - "migration_0005_not_enough_space": "Not enough space is available in {path} to run the migration right now :(.", + "migration_0005_postgresql_96_not_installed": "Postgresql 9.4 has been found to be installed, but not postgresql 9.6!? Something weird might have happened on your system:(…", + "migration_0005_not_enough_space": "Not enough space is available in {path} to run the migration right now:(.", "migration_0006_disclaimer": "Yunohost now expects admin and root passwords to be synchronized. By running this migration, your root password is going to be replaced by the admin password.", "migration_0007_cancelled": "YunoHost has failed to improve the way your SSH conf is managed.", "migration_0007_cannot_restart": "SSH can't be restarted after trying to cancel migration number 6.", "migration_0008_general_disclaimer": "To improve the security of your server, it is recommended to let YunoHost manage the SSH configuration. Your current SSH configuration differs from the recommended configuration. If you let YunoHost reconfigure it, the way you connect to your server through SSH will change in the following way:", - "migration_0008_port": " - you will have to connect using port 22 instead of your current custom SSH port. Feel free to reconfigure it ;", - "migration_0008_root": " - you will not be able to connect as root through SSH. Instead you should use the admin user ;", - "migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server ;", + "migration_0008_port": " - you will have to connect using port 22 instead of your current custom SSH port. Feel free to reconfigure it;", + "migration_0008_root": " - you will not be able to connect as root through SSH. Instead you should use the admin user;", + "migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", - "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;) ! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", + "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migrations_backward": "Migrating backward.", "migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", @@ -315,12 +315,12 @@ "migrations_error_failed_to_load_migration": "ERROR: failed to load migration {number} {name}", "migrations_forward": "Migrating forward", "migrations_list_conflict_pending_done": "You cannot use both --previous and --done at the same time.", - "migrations_loading_migration": "Loading migration {number} {name}...", + "migrations_loading_migration": "Loading migration {number} {name}…", "migrations_migration_has_failed": "Migration {number} {name} has failed with exception {exception}, aborting", "migrations_no_migrations_to_run": "No migrations to run", - "migrations_show_currently_running_migration": "Running migration {number} {name}...", + "migrations_show_currently_running_migration": "Running migration {number} {name}…", "migrations_show_last_migration": "Last ran migration is {}", - "migrations_skip_migration": "Skipping migration {number} {name}...", + "migrations_skip_migration": "Skipping migration {number} {name}…", "migrations_success": "Successfully ran migration {number} {name}!", "migrations_to_be_ran_manually": "Migration {number} {name} has to be ran manually. Please go to Tools > Migrations on the webadmin, or run `yunohost tools migrations migrate`.", "migrations_need_to_accept_disclaimer": "To run the migration {number} {name}, your must accept the following disclaimer:\n---\n{disclaimer}\n---\nIf you accept to run the migration, please re-run the command with the option --accept-disclaimer.", @@ -380,7 +380,7 @@ "restore_cleaning_failed": "Unable to clean-up the temporary restoration directory", "restore_complete": "Restore complete", "restore_confirm_yunohost_installed": "Do you really want to restore an already installed system? [{answers:s}]", - "restore_extracting": "Extracting needed files from the archive...", + "restore_extracting": "Extracting needed files from the archive…", "restore_failed": "Unable to restore the system", "restore_hook_unavailable": "Restoration script for '{part:s}' not available on your system and not in the archive either", "restore_may_be_not_enough_disk_space": "Your system seems not to have enough disk space (freespace: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", @@ -388,10 +388,10 @@ "restore_not_enough_disk_space": "Not enough disk space (freespace: {free_space:d} B, needed space: {needed_space:d} B, security margin: {margin:d} B)", "restore_nothings_done": "Nothing has been restored", "restore_removing_tmp_dir_failed": "Unable to remove an old temporary directory", - "restore_running_app_script": "Running restore script of app '{app:s}'...", - "restore_running_hooks": "Running restoration hooks...", + "restore_running_app_script": "Running restore script of app '{app:s}'…", + "restore_running_hooks": "Running restoration hooks…", "restore_system_part_failed": "Unable to restore the '{part:s}' system part", - "root_password_desynchronized": "The admin password has been changed, but YunoHost was unable to propagate this on the root password !", + "root_password_desynchronized": "The admin password has been changed, but YunoHost was unable to propagate this on the root password!", "root_password_replaced_by_admin_password": "Your root password have been replaced by your admin password.", "server_shutdown": "The server will shutdown", "server_shutdown_confirm": "The server will shutdown immediatly, are you sure? [{answers:s}]", @@ -437,9 +437,9 @@ "service_enable_failed": "Unable to enable service '{service:s}'\n\nRecent service logs:{logs:s}", "service_enabled": "The service '{service:s}' has been enabled", "service_no_log": "No log to display for service '{service:s}'", - "service_regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for service '{service}'...", + "service_regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for service '{service}'…", "service_regenconf_failed": "Unable to regenerate the configuration for service(s): {services}", - "service_regenconf_pending_applying": "Applying pending configuration for service '{service}'...", + "service_regenconf_pending_applying": "Applying pending configuration for service '{service}'…", "service_remove_failed": "Unable to remove service '{service:s}'", "service_removed": "The service '{service:s}' has been removed", "service_start_failed": "Unable to start service '{service:s}'\n\nRecent service logs:{logs:s}", @@ -460,9 +460,9 @@ "unlimit": "No quota", "unrestore_app": "App '{app:s}' will not be restored", "update_cache_failed": "Unable to update APT cache", - "updating_apt_cache": "Updating the list of available packages...", + "updating_apt_cache": "Updating the list of available packages…", "upgrade_complete": "Upgrade complete", - "upgrading_packages": "Upgrading packages...", + "upgrading_packages": "Upgrading packages…", "upnp_dev_not_found": "No UPnP device found", "upnp_disabled": "UPnP has been disabled", "upnp_enabled": "UPnP has been enabled", @@ -481,6 +481,6 @@ "yunohost_ca_creation_failed": "Unable to create certificate authority", "yunohost_ca_creation_success": "The local certification authority has been created.", "yunohost_configured": "YunoHost has been configured", - "yunohost_installing": "Installing YunoHost...", + "yunohost_installing": "Installing YunoHost…", "yunohost_not_installed": "YunoHost is not or not correctly installed. Please execute 'yunohost tools postinstall'" } From 9fb302fe043a368d27f18ca05cd843841a2ef5f2 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 13 Jan 2019 14:24:17 +0100 Subject: [PATCH 069/721] [fix] Replace manifest_path by manifest manifest_path isn't defined --- data/helpers.d/system | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index f09343953..a93b3ea6f 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -85,10 +85,10 @@ ynh_app_upstream_version () { ynh_handle_getopts_args "$@" manifest="${manifest:-../manifest.json}" - if [ ! -e "$manifest_path" ]; then - manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place + if [ ! -e "$manifest" ]; then + manifest="../settings/manifest.json" # Into the restore script, the manifest is not at the same place fi - version_key=$(ynh_read_manifest --manifest="$manifest_path" --manifest_key="version") + version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") echo "${version_key/~ynh*/}" } @@ -107,10 +107,10 @@ ynh_app_package_version () { ynh_handle_getopts_args "$@" manifest="${manifest:-../manifest.json}" - if [ ! -e "$manifest_path" ]; then - manifest_path="../settings/manifest.json" # Into the restore script, the manifest is not at the same place + if [ ! -e "$manifest" ]; then + manifest="../settings/manifest.json" # Into the restore script, the manifest is not at the same place fi - version_key=$(ynh_read_manifest --manifest="$manifest_path" --manifest_key="version") + version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") echo "${version_key/*~ynh/}" } From 4760cc0ba66675ec9c698af4996f7886c172194f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 14 Jan 2019 18:49:30 +0100 Subject: [PATCH 070/721] [fix] Microdecision: typo, 'dest' did not exist --- src/yunohost/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 745291fb1..d98250629 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1746,8 +1746,8 @@ class CopyBackupMethod(BackupMethod): return else: logger.warning(m18n.n("bind_mouting_disable")) - subprocess.call(["mountpoint", "-q", dest, - "&&", "umount", "-R", dest]) + subprocess.call(["mountpoint", "-q", self.work_dir, + "&&", "umount", "-R", self.work_dir]) raise YunohostError('backup_cant_mount_uncompress_archive') From 1c7f0d173a4ddd8545dd56cca5573f0287af7f49 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 00:24:02 +0000 Subject: [PATCH 071/721] Fix UX when change_url args get asked interactively --- data/actionsmap/yunohost.yml | 4 ++-- locales/en.json | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 08478e2b4..a8a2336cf 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -606,14 +606,14 @@ app: full: --domain help: New app domain on which the application will be moved extra: - ask: ask_main_domain + ask: ask_new_domain pattern: *pattern_domain required: True -p: full: --path help: New path at which the application will be moved extra: - ask: ask_path + ask: ask_new_path required: True ### app_setting() diff --git a/locales/en.json b/locales/en.json index 6e4fda2ac..090dd5940 100644 --- a/locales/en.json +++ b/locales/en.json @@ -57,6 +57,8 @@ "ask_list_to_remove": "List to remove", "ask_main_domain": "Main domain", "ask_new_admin_password": "New administration password", + "ask_new_domain": "New domain", + "ask_new_path": "New path", "ask_password": "Password", "ask_path": "Path", "backup_abstract_method": "This backup method hasn't yet been implemented", From f4d728465a1171054e8e77baa8922615dd9c67d2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 02:10:51 +0100 Subject: [PATCH 072/721] Enforce --force-confold during apt upgrade (#614) * Enforce --force-confold during apt upgrade * Update src/yunohost/tools.py Co-Authored-By: alexAubin * Not sure why but every piece of information about confold also set confdef :/ * Export DEBIAN_FRONTEND="noninteractive" during upgrade --- src/yunohost/tools.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 89bd303bf..189b1db09 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -530,6 +530,11 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal is_api = True if msettings.get('interface') == 'api' else False if not ignore_packages: + + apt.apt_pkg.init() + apt.apt_pkg.config.set("DPkg::Options::", "--force-confdef") + apt.apt_pkg.config.set("DPkg::Options::", "--force-confold") + cache = apt.Cache() cache.open(None) cache.upgrade(True) @@ -558,6 +563,7 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal operation_logger.start() try: + os.environ["DEBIAN_FRONTEND"] = "noninteractive" # Apply APT changes # TODO: Logs output for the API cache.commit(apt.progress.text.AcquireProgress(), @@ -570,6 +576,8 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal else: logger.info(m18n.n('done')) operation_logger.success() + finally: + del os.environ["DEBIAN_FRONTEND"] else: logger.info(m18n.n('packages_no_upgrade')) From 9caa771428576538bf2a432f394817de50c2e9df Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 02:10:51 +0100 Subject: [PATCH 073/721] Enforce --force-confold during apt upgrade (#614) * Enforce --force-confold during apt upgrade * Update src/yunohost/tools.py Co-Authored-By: alexAubin * Not sure why but every piece of information about confold also set confdef :/ * Export DEBIAN_FRONTEND="noninteractive" during upgrade --- src/yunohost/tools.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 78e641189..383ffdba1 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -521,6 +521,11 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal is_api = True if msettings.get('interface') == 'api' else False if not ignore_packages: + + apt.apt_pkg.init() + apt.apt_pkg.config.set("DPkg::Options::", "--force-confdef") + apt.apt_pkg.config.set("DPkg::Options::", "--force-confold") + cache = apt.Cache() cache.open(None) cache.upgrade(True) @@ -549,6 +554,7 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal operation_logger.start() try: + os.environ["DEBIAN_FRONTEND"] = "noninteractive" # Apply APT changes # TODO: Logs output for the API cache.commit(apt.progress.text.AcquireProgress(), @@ -561,6 +567,8 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal else: logger.info(m18n.n('done')) operation_logger.success() + finally: + del os.environ["DEBIAN_FRONTEND"] else: logger.info(m18n.n('packages_no_upgrade')) From 6a7f5b2bbec92ddfbb7d1b8967420c18066a484e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 02:14:35 +0100 Subject: [PATCH 074/721] Update changelog for 3.3.4 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 3abd967e1..0dbc1e028 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.3.4) stable; urgency=low + + * [fix] Use --force-confold and noninteractive debian frontend during core upgrade (#614) + + -- Alexandre Aubin Thu, 17 Jan 2019 02:00:00 +0000 + yunohost (3.3.3) stable; urgency=low * [fix] ynh_wait_dpkg_free displaying a warning despite everything being okay (#593) From 9632164c43b17816f521f7ad8ec1f6c9dd3104cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 16:11:39 +0100 Subject: [PATCH 075/721] Set owner of archives folder to 'admin' (#613) --- src/yunohost/backup.py | 6 ++++-- .../0008_ssh_conf_managed_by_yunohost_step2.py | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index d98250629..0c8445bd6 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -40,7 +40,7 @@ from moulinette import msignals, m18n from yunohost.utils.error import YunohostError from moulinette.utils import filesystem from moulinette.utils.log import getActionLogger -from moulinette.utils.filesystem import read_file +from moulinette.utils.filesystem import read_file, mkdir from yunohost.app import ( app_info, _is_installed, _parse_app_instance_name, _patch_php5 @@ -2295,7 +2295,9 @@ def _create_archive_dir(): if os.path.lexists(ARCHIVES_PATH): raise YunohostError('backup_output_symlink_dir_broken', path=ARCHIVES_PATH) - os.mkdir(ARCHIVES_PATH, 0o750) + # Create the archive folder, with 'admin' as owner, such that + # people can scp archives out of the server + mkdir(ARCHIVES_PATH, mode=0o750, parents=True, uid="admin", gid="root") def _call_for_each_path(self, callback, csv_path=None): diff --git a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py index 11b450479..0abb18a26 100644 --- a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py +++ b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py @@ -2,6 +2,7 @@ import re from moulinette import m18n from moulinette.utils.log import getActionLogger +from moulinette.utils.filesystem import chown from yunohost.tools import Migration from yunohost.service import service_regen_conf, \ @@ -9,6 +10,8 @@ from yunohost.service import service_regen_conf, \ _calculate_hash from yunohost.settings import settings_set, settings_get from yunohost.utils.error import YunohostError +from yunohost.backup import ARCHIVES_PATH + logger = getActionLogger('yunohost.migration') @@ -34,6 +37,10 @@ class MyMigration(Migration): settings_set("service.ssh.allow_deprecated_dsa_hostkey", False) service_regen_conf(names=['ssh'], force=True) + # Update local archives folder permissions, so that + # admin can scp archives out of the server + chown(ARCHIVES_PATH, uid="admin", gid="root") + def backward(self): raise YunohostError("migration_0008_backward_impossible") From 808d844f8420d287cc1bd5cc4baac7f9adf1eba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Martin?= Date: Thu, 17 Jan 2019 16:12:21 +0100 Subject: [PATCH 076/721] Change git clone for gitlab working with branch (#615) --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 82bda03cd..3925a86db 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1907,7 +1907,7 @@ def _fetch_app_from_git(app): # we will be able to use it. Without this option all the history # of the submodules repo is downloaded. subprocess.check_call([ - 'git', 'clone', '--depth=1', '--recursive', url, + 'git', 'clone', '-b', branch, '--single-branch', '--recursive', '--depth=1', url, extracted_app_folder]) subprocess.check_call([ 'git', 'reset', '--hard', branch From bfe505672072a0bcf00401c05e87c235b64f3bb5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 16:12:49 +0100 Subject: [PATCH 077/721] [fix] _run_service_command was not properly returning False if command failed (#616) --- src/yunohost/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index d9bd39fba..973c89362 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -663,7 +663,7 @@ def _run_service_command(action, service): try: # Launch the command logger.debug("Running '%s'" % cmd) - p = subprocess.Popen(cmd.split(), stderr=subprocess.STDOUT) + p = subprocess.check_call(cmd.split(), stderr=subprocess.STDOUT) # If this command needs a lock (because the service uses yunohost # commands inside), find the PID and add a lock for it if need_lock: From 1667ba14e3fe5d35401ad83b2a9ca6abf25179b0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 16:52:47 +0100 Subject: [PATCH 078/721] [fix] Explicit dependance to iptables (it's only a recommended package of fail2ban, gotta make sure it will really be installed) --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 9f72bf11a..b359d5ec4 100644 --- a/debian/control +++ b/debian/control @@ -20,7 +20,7 @@ Depends: ${python:Depends}, ${misc:Depends} , slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd , postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail, mailutils, postsrsd , dovecot-ldap, dovecot-lmtpd, dovecot-managesieved - , dovecot-antispam, fail2ban + , dovecot-antispam, fail2ban, iptables , nginx-extras (>=1.6.2), php-fpm, php-ldap, php-intl , dnsmasq, openssl, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname , metronome From aa6b97ca2ce102d06a6f6850c4da63c6be777c31 Mon Sep 17 00:00:00 2001 From: Janos Meggyeshazi Date: Thu, 27 Dec 2018 20:47:36 +0000 Subject: [PATCH 079/721] Added translation using Weblate (Hungarian) --- locales/hu.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/hu.json diff --git a/locales/hu.json b/locales/hu.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/hu.json @@ -0,0 +1 @@ +{} From e81dbad73d339d04dd7d7b155225f848d84af32a Mon Sep 17 00:00:00 2001 From: anubis Date: Thu, 27 Dec 2018 10:50:19 +0000 Subject: [PATCH 080/721] Translated using Weblate (Esperanto) Currently translated at 2.3% (11 of 465 strings) --- locales/eo.json | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index 0967ef424..f341c27b7 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -1 +1,20 @@ -{} +{ + "admin_password_change_failed": "Malebla ŝanĝi pasvorton", + "admin_password_changed": "Pasvorto de la estro estas ŝanĝita", + "app_already_installed": "{app:s} estas jam instalita", + "app_already_up_to_date": "{app:s} estas ĝisdata", + "app_argument_required": "Parametro {name:s} estas bezonata", + "app_change_url_identical_domains": "Malnovaj kaj novaj domajno/URL estas la sama ('{domain:s}{path:s}'), nenio fareblas.", + "app_change_url_success": "URL de appo {app:s} ŝanĝita al {domain:s}{path:s}", + "app_extraction_failed": "Malebla malkompaktigi instaldosierojn", + "app_id_invalid": "Nevalida apo id", + "app_incompatible": "Apo {app} ne estas kongrua kun via YunoHost versio", + "app_install_files_invalid": "Nevalidaj instaldosieroj", + "app_location_already_used": "Apo {app} jam estas instalita al tiu loco ({path})", + "user_updated": "Uzanto estas ĝisdatita", + "users_available": "Uzantoj disponeblaj :", + "yunohost_already_installed": "YunoHost estas jam instalita", + "yunohost_ca_creation_failed": "Ne eblas krei atestan aŭtoritaton", + "yunohost_ca_creation_success": "Loka atesta aŭtoritato estas kreita.", + "yunohost_installing": "Instalata YunoHost..." +} From df27a32914ffa5aa647424d7af929ab8723d27c6 Mon Sep 17 00:00:00 2001 From: Janos Meggyeshazi Date: Thu, 27 Dec 2018 21:50:08 +0000 Subject: [PATCH 081/721] Translated using Weblate (Hungarian) Currently translated at 0.2% (1 of 483 strings) --- locales/hu.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/hu.json b/locales/hu.json index 0967ef424..1204ff784 100644 --- a/locales/hu.json +++ b/locales/hu.json @@ -1 +1,3 @@ -{} +{ + "aborting": "Megszakítás" +} From 7477b24a18a4bc98c61439aa8be037b3d68f3210 Mon Sep 17 00:00:00 2001 From: Janos Meggyeshazi Date: Thu, 27 Dec 2018 21:50:27 +0000 Subject: [PATCH 082/721] Translated using Weblate (Hungarian) Currently translated at 2.2% (11 of 483 strings) --- locales/hu.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/locales/hu.json b/locales/hu.json index 1204ff784..fe91f2f0d 100644 --- a/locales/hu.json +++ b/locales/hu.json @@ -1,3 +1,13 @@ { - "aborting": "Megszakítás" + "aborting": "Megszakítás.", + "action_invalid": "Érvénytelen művelet '{action:s}'", + "admin_password": "Adminisztrátori jelszó", + "admin_password_change_failed": "Nem lehet a jelszót megváltoztatni", + "admin_password_changed": "Az adminisztrátori jelszó megváltozott", + "app_already_installed": "{app:s} már telepítve van", + "app_already_installed_cant_change_url": "Ez az app már telepítve van. Ezzel a funkcióval az url nem változtatható. Javaslat 'app url változtatás' ha lehetséges.", + "app_already_up_to_date": "{app:s} napra kész", + "app_argument_choice_invalid": "{name:s} érvénytelen választás, csak egyike lehet {choices:s} közül", + "app_argument_invalid": "{name:s} hibás paraméter érték :{error:s}", + "app_argument_required": "Parameter '{name:s}' kötelező" } From 4dcec91321d8f0c083cb4bb361560df07d3004d6 Mon Sep 17 00:00:00 2001 From: Janos Meggyeshazi Date: Thu, 27 Dec 2018 22:08:40 +0000 Subject: [PATCH 083/721] Translated using Weblate (Hungarian) Currently translated at 2.2% (11 of 483 strings) --- locales/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/hu.json b/locales/hu.json index fe91f2f0d..a6df4d680 100644 --- a/locales/hu.json +++ b/locales/hu.json @@ -8,6 +8,6 @@ "app_already_installed_cant_change_url": "Ez az app már telepítve van. Ezzel a funkcióval az url nem változtatható. Javaslat 'app url változtatás' ha lehetséges.", "app_already_up_to_date": "{app:s} napra kész", "app_argument_choice_invalid": "{name:s} érvénytelen választás, csak egyike lehet {choices:s} közül", - "app_argument_invalid": "{name:s} hibás paraméter érték :{error:s}", + "app_argument_invalid": "'{name:s}' hibás paraméter érték :{error:s}", "app_argument_required": "Parameter '{name:s}' kötelező" } From e34431ee7bc2479690da0fb295545db19eada6e2 Mon Sep 17 00:00:00 2001 From: anubis Date: Thu, 27 Dec 2018 20:44:11 +0000 Subject: [PATCH 084/721] Translated using Weblate (German) Currently translated at 62.7% (292 of 465 strings) --- locales/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/de.json b/locales/de.json index 8174e258e..a4a6c236b 100644 --- a/locales/de.json +++ b/locales/de.json @@ -12,7 +12,7 @@ "app_install_files_invalid": "Ungültige Installationsdateien", "app_location_already_used": "Eine andere App ({app}) ist bereits an diesem Ort ({path}) installiert", "app_location_install_failed": "Die App kann nicht an diesem Ort installiert werden, da es mit der App {other_app} die bereits in diesem Pfad ({other_path}) installiert ist Probleme geben würde", - "app_manifest_invalid": "Ungültiges App-Manifest", + "app_manifest_invalid": "Ungültiges App-Manifest: {error}", "app_no_upgrade": "Keine Aktualisierungen für Apps verfügbar", "app_not_installed": "{app:s} ist nicht installiert", "app_recent_version_required": "Für {:s} benötigt eine aktuellere Version von moulinette", @@ -294,7 +294,7 @@ "backup_applying_method_tar": "Erstellen des Backup-tar Archives...", "backup_applying_method_copy": "Kopiere alle Dateien ins Backup...", "app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung der Anwendung.", - "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt", + "app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt:\n{apps:s}", "backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf...", "backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten", "backup_archive_mount_failed": "Das Einbinden des Backup-Archives ist fehlgeschlagen", From 81793d5948040ddc42a27d1d25f31c3ab5e5b92a Mon Sep 17 00:00:00 2001 From: anubis Date: Sat, 29 Dec 2018 12:07:33 +0000 Subject: [PATCH 085/721] Translated using Weblate (Esperanto) Currently translated at 3.4% (16 of 465 strings) --- locales/eo.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index f341c27b7..c5d0341fe 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -16,5 +16,11 @@ "yunohost_already_installed": "YunoHost estas jam instalita", "yunohost_ca_creation_failed": "Ne eblas krei atestan aŭtoritaton", "yunohost_ca_creation_success": "Loka atesta aŭtoritato estas kreita.", - "yunohost_installing": "Instalata YunoHost..." + "yunohost_installing": "Instalata YunoHost...", + "service_description_glances": "monitoras sisteminformojn de via servilo", + "service_description_metronome": "mastrumas XMPP tujmesaĝilon kontojn", + "service_description_mysql": "stokas aplikaĵojn datojn (SQL datumbazo)", + "service_description_nginx": "servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo", + "service_description_nslcd": "mastrumas Yunohost uzantojn konektojn per komanda linio", + "service_description_php7.0-fpm": "rulas aplikaĵojn skibitaj en PHP kun nginx" } From 2bfc0342c78e5e48ccdcbb54991a825b24cb7a52 Mon Sep 17 00:00:00 2001 From: anubis Date: Sat, 29 Dec 2018 12:29:28 +0000 Subject: [PATCH 086/721] Translated using Weblate (Esperanto) Currently translated at 5.3% (25 of 465 strings) --- locales/eo.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index c5d0341fe..6a7a82784 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -22,5 +22,15 @@ "service_description_mysql": "stokas aplikaĵojn datojn (SQL datumbazo)", "service_description_nginx": "servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo", "service_description_nslcd": "mastrumas Yunohost uzantojn konektojn per komanda linio", - "service_description_php7.0-fpm": "rulas aplikaĵojn skibitaj en PHP kun nginx" + "service_description_php7.0-fpm": "rulas aplikaĵojn skibita en PHP kun nginx", + "service_description_postfix": "uzita por sendi kaj ricevi retpoŝtojn", + "service_description_redis-server": "specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj", + "service_description_rmilter": "kontrolas diversajn parametrojn en retpoŝtoj", + "service_description_rspamd": "filtras trudmesaĝojn, kaj aliaj funkcioj rilate al retpoŝto", + "service_description_slapd": "stokas uzantojn, domajnojn kaj rilatajn informojn", + "service_description_ssh": "permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)", + "service_description_yunohost-api": "mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo", + "service_description_yunohost-firewall": "mastrumas malfermitajn kaj fermitajn konektejojn al servoj", + "service_disable_failed": "Neebla malaktivigi servon '{service:s}'\n\nFreŝaj protokoloj de la servo : {logs:s}", + "service_disabled": "Servo '{service:s}' estas malaktivigita" } From 442bd8c3e4343af9fa9d33c12512dc5b54848b92 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 20:12:09 +0000 Subject: [PATCH 087/721] [fix] Woopsies ... cannot use check_call because we need a non-block call. Also Popen won't trigger a CalledProcessError --- src/yunohost/service.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 973c89362..fcd9453bc 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -663,7 +663,7 @@ def _run_service_command(action, service): try: # Launch the command logger.debug("Running '%s'" % cmd) - p = subprocess.check_call(cmd.split(), stderr=subprocess.STDOUT) + p = subprocess.Popen(cmd.split(), stderr=subprocess.STDOUT) # If this command needs a lock (because the service uses yunohost # commands inside), find the PID and add a lock for it if need_lock: @@ -671,10 +671,9 @@ def _run_service_command(action, service): # Wait for the command to complete p.communicate() - except subprocess.CalledProcessError as e: - # TODO: Log output? - logger.warning(m18n.n('service_cmd_exec_failed', command=' '.join(e.cmd))) - return False + if p.returncode != 0: + logger.warning(m18n.n('service_cmd_exec_failed', command=cmd)) + return False finally: # Remove the lock if one was given From 836083a62e6d089e48d661477cc31fd2bd006b46 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 20:15:18 +0000 Subject: [PATCH 088/721] Handle unexpected errors... --- src/yunohost/service.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index fcd9453bc..302ad651e 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -675,6 +675,10 @@ def _run_service_command(action, service): logger.warning(m18n.n('service_cmd_exec_failed', command=cmd)) return False + except Exception as e: + logger.warning(m18n.n("unexpected_error", error=str(e))) + return False + finally: # Remove the lock if one was given if need_lock and PID != 0: From d975ed2689b28134442b83a2c3d135da17732bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 22:31:40 +0100 Subject: [PATCH 089/721] Update LDAP config --- data/hooks/conf_regen/06-slapd | 2 +- data/other/ldap_scheme.yml | 53 +++++++++++++++++++--------- data/templates/slapd/slapd.conf | 32 ++++++++++++++++- data/templates/slapd/yunohost.schema | 33 +++++++++++++++++ src/yunohost/tools.py | 6 ++++ 5 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 data/templates/slapd/yunohost.schema diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index d0a1fad63..9ba223e4c 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -60,7 +60,7 @@ do_pre_regen() { # copy configuration files cp -a ldap.conf slapd.conf "$ldap_dir" - cp -a sudo.schema mailserver.schema "$schema_dir" + cp -a sudo.schema mailserver.schema yunohost.schema "$schema_dir" install -D -m 644 slapd.default "${pending_dir}/etc/default/slapd" } diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index 75bdea6e2..d30c4915c 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -17,6 +17,12 @@ parents: - organizationalUnit - top + ou=permission: + ou: permission + objectClass: + - organizationalUnit + - top + ou=groups: ou: groups objectClass: @@ -29,22 +35,6 @@ parents: - top children: - cn=admins,ou=groups: - cn: admins - gidNumber: "4001" - memberUid: admin - objectClass: - - posixGroup - - top - - cn=sftpusers,ou=groups: - cn: sftpusers - gidNumber: "4002" - memberUid: admin - objectClass: - - posixGroup - - top - cn=admin,ou=sudo: cn: admin sudoUser: admin @@ -54,3 +44,34 @@ children: objectClass: - sudoRole - top + cn=admins,ou=groups: + cn: admins + gidNumber: "4001" + memberUid: admin + objectClass: + - posixGroup + - top + cn=ALL,ou=groups: + cn: ALL + gidNumber: "4002" + objectClass: + - posixGroup + - groupOfNamesYnh + +depends_children: + cn=main.mail,ou=permission: + cn: main.mail + gidNumber: "5001" + objectClass: + - posixGroup + - permissionYnh + groupPermission: + - "cn=ALL,ou=groups,dc=yunohost,dc=org" + cn=main.metronome,ou=permission: + cn: main.metronome + gidNumber: "5002" + objectClass: + - posixGroup + - permissionYnh + groupPermission: + - "cn=ALL,ou=groups,dc=yunohost,dc=org" diff --git a/data/templates/slapd/slapd.conf b/data/templates/slapd/slapd.conf index 9a8800d9d..4acebe97e 100644 --- a/data/templates/slapd/slapd.conf +++ b/data/templates/slapd/slapd.conf @@ -14,6 +14,7 @@ include /etc/ldap/schema/nis.schema include /etc/ldap/schema/inetorgperson.schema include /etc/ldap/schema/mailserver.schema include /etc/ldap/schema/sudo.schema +include /etc/ldap/schema/yunohost.schema # Where the pid file is put. The init.d script # will not stop the server if you change this. @@ -31,7 +32,7 @@ password-hash {SSHA} # Where the dynamically loaded modules are stored modulepath /usr/lib/ldap moduleload back_mdb -moduleload memberof +moduleload memberof # The maximum number of entries that is returned for a search operation sizelimit 500 @@ -110,3 +111,32 @@ access to * by dn="cn=admin,dc=yunohost,dc=org" write by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write by * read + +# Configure Memberof Overlay (used for Yunohost permission) + +# Link user <-> group +#dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config +overlay memberof +memberof-group-oc groupOfNamesYnh +memberof-member-ad member +memberof-memberof-ad memberOf +memberof-dangling error +memberof-refint TRUE + +# Link permission <-> groupes +#dn: olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config +overlay memberof +memberof-group-oc permissionYnh +memberof-member-ad groupPermission +memberof-memberof-ad permission +memberof-dangling error +memberof-refint TRUE + +# Link permission <-> user +#dn: olcOverlay={2}memberof,olcDatabase={1}mdb,cn=config +overlay memberof +memberof-group-oc permissionYnh +memberof-member-ad inheritPermission +memberof-memberof-ad permission +memberof-dangling error +memberof-refint TRUE diff --git a/data/templates/slapd/yunohost.schema b/data/templates/slapd/yunohost.schema new file mode 100644 index 000000000..7da60a20c --- /dev/null +++ b/data/templates/slapd/yunohost.schema @@ -0,0 +1,33 @@ +#dn: cn=yunohost,cn=schema,cn=config +#objectClass: olcSchemaConfig +#cn: yunohost +# ATTRIBUTES +# For Permission +attributetype ( 1.3.6.1.4.1.17953.9.1.1 NAME 'permission' + DESC 'Yunohost permission on user and group side' + SUP distinguishedName ) +attributetype ( 1.3.6.1.4.1.17953.9.1.2 NAME 'groupPermission' + DESC 'Yunohost permission for a group on permission side' + SUP distinguishedName ) +attributetype ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission' + DESC 'Yunohost permission for user on permission side' + SUP distinguishedName ) +attributetype ( 1.3.6.1.4.1.17953.9.1.4 NAME 'URL' + DESC 'Yunohost application URL' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) +# OBJECTCLASS +# For Applications +objectclass ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh' + DESC 'Yunohost user group' + SUP top AUXILIARY + MAY ( member $ businessCategory $ seeAlso $ owner $ ou $ o $ permission ) ) +objectclass ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh' + DESC 'a Yunohost application' + SUP top AUXILIARY + MUST cn + MAY ( groupPermission $ inheritPermission $ URL ) ) +# For User +objectclass ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh' + DESC 'a Yunohost application' + SUP top AUXILIARY + MAY ( permission ) ) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 189b1db09..d58951878 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -89,6 +89,12 @@ def tools_ldapinit(): except Exception as e: logger.warn("Error when trying to inject '%s' -> '%s' into ldap: %s" % (rdn, attr_dict, e)) + for rdn, attr_dict in ldap_map['depends_children'].items(): + try: + auth.add(rdn, attr_dict) + except Exception as e: + logger.warn("Error when trying to inject '%s' -> '%s' into ldap: %s" % (rdn, attr_dict, e)) + admin_dict = { 'cn': 'admin', 'uid': 'admin', From 7b8d6688466469dbca2b3a6aa4dde184a05a00d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 22:47:02 +0100 Subject: [PATCH 090/721] Update actionsmap for group and permissions --- data/actionsmap/yunohost.yml | 189 ++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 38e311546..afd2ed3b3 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -63,7 +63,7 @@ _global: # User # ############################# user: - category_help: Manage users + category_help: Manage users and groups actions: ### user_list() @@ -217,6 +217,193 @@ user: help: Username or email to get information subcategories: + group: + subcategory_help: Manage group + actions: + ### user_group_list() + list: + action_help: List group + api: GET /users/groups + configuration: + authenticate: all + authenticator: ldap-anonymous + arguments: + --fields: + help: fields to fetch + nargs: "+" + + ### user_group_add() + add: + action_help: Create group + api: POST /users/groups + configuration: + authenticate: all + arguments: + groupname: + help: The unique group name to add + extra: + pattern: &pattern_groupname + - !!str ^[a-z0-9_]+$ + - "pattern_groupname" + + ### user_group_delete() + delete: + action_help: Delete group + api: DELETE /users/groups/ + configuration: + authenticate: all + arguments: + groupname: + help: Username to delete + extra: + pattern: *pattern_groupname + + ### user_group_update() + update: + action_help: Update group + api: PUT /users/groups/ + configuration: + authenticate: all + arguments: + groupname: + help: Username to update + extra: + pattern: *pattern_groupname + -a: + full: --add-user + help: User to add in group + nargs: "*" + metavar: USERNAME + extra: + pattern: *pattern_username + -r: + full: --remove-user + help: User to remove in group + nargs: "*" + metavar: USERNAME + extra: + pattern: *pattern_username + + ### user_group_info() + info: + action_help: Get group information + api: GET /users/ + configuration: + authenticate: all + authenticator: ldap-anonymous + arguments: + groupname: + help: Groupname to get information + extra: + pattern: *pattern_username + + permission: + subcategory_help: Manage user permission + actions: + ### user_permission_list() + list: + action_help: List access to user and group + api: GET /users/permission/ + configuration: + authenticate: all + authenticator: ldap-anonymous + arguments: + -a: + full: --app + help: Application to manage the permission + nargs: "*" + metavar: APP + -p: + full: --permission + help: Name of permission (main by default) + nargs: "*" + metavar: PERMISSION + -u: + full: --username + help: Username + nargs: "*" + metavar: USER + -g: + full: --group + help: Group name + nargs: "*" + metavar: GROUP + + ### user_permission_add() + add: + action_help: Grant access right to users and group + api: POST /users/permission/ + configuration: + authenticate: all + arguments: + app: + help: Application to manage the permission + nargs: "*" + -p: + full: --permission + help: Name of permission (main by default) + nargs: "*" + metavar: PERMISSION + -u: + full: --username + help: Username + nargs: "*" + metavar: USER + extra: + pattern: *pattern_username + -g: + full: --group + help: Group name + nargs: "*" + metavar: GROUP + extra: + pattern: *pattern_username + + ### user_permission_remove() + remove: + action_help: Revoke access right to users and group + api: PUT /users/permission/ + configuration: + authenticate: all + arguments: + app: + help: Application to manage the permission + nargs: "*" + -p: + full: --permission + help: Name of permission (main by default) + nargs: "*" + metavar: PERMISSION + -u: + full: --username + help: Username + nargs: "*" + metavar: USER + extra: + pattern: *pattern_username + -g: + full: --group + help: Group name + nargs: "*" + metavar: GROUP + extra: + pattern: *pattern_username + + ## user_permission_clear() + clear: + action_help: Reset access rights for the app + api: DELETE /users/permission/ + configuration: + authenticate: all + arguments: + app: + help: Application to manage the permission + nargs: "*" + -p: + full: --permission + help: Name of permission (main by default) + nargs: "*" + metavar: PERMISSION ssh: subcategory_help: Manage ssh access From bb892bb1a49a5561f8e370b80d4131e268384334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 22:50:48 +0100 Subject: [PATCH 091/721] Implement group management --- locales/en.json | 35 +++++ src/yunohost/user.py | 342 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 346 insertions(+), 31 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5ef9f5c0c..5b78cdcd7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -180,6 +180,7 @@ "dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}", "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", "dyndns_unavailable": "Domain {domain:s} is not available.", + "edit_group_not_allowed": "You are not allowed to edit the group {group:s}", "executing_command": "Executing command '{command:s}'…", "executing_script": "Executing script '{script:s}'…", "extracting": "Extracting…", @@ -207,6 +208,18 @@ "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", + "group_alread_allowed": "Group '{group:s}' already allowed to access to permission '{permission:s}' for app '{app:s}'", + "group_alread_disallowed": "Group '{group:s}' already disallowed to access to permission '{permission:s}' for app '{app:s}'", + "group_name_already_exist": "Group {name:s} already exist", + "group_created": "Group creation success", + "group_creation_failed": "Group creation failed", + "group_deleted": "Group deleted", + "group_deletion_failed": "Group deletion failed", + "group_deletion_not_allowed": "You are not allowed to remove the main group of the user {user:s}", + "group_info_failed": "Group info failed", + "group_unknown": "Groupe {group:s} unknown", + "group_updated": "Groupe updated", + "group_update_failed": "groupe update failed", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", "hook_list_by_invalid": "Invalid property to list hook by", @@ -244,13 +257,20 @@ "log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'", "log_dyndns_update": "Update the ip associated with your YunoHost subdomain '{}'", "log_letsencrypt_cert_install": "Install Let's encrypt certificate on '{}' domain", + "log_permission_add": "Add permission '{}' for app '{}'", + "log_permission_remove": "Remove permission '{}'", + "log_permission_update": "Update permission '{}' for app '{}'", "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_service_enable": "Enable '{}' service", "log_service_regen_conf": "Regenerate system configurations '{}'", "log_user_create": "Add '{}' user", "log_user_delete": "Delete '{}' user", + "log_user_group_add": "Add '{}' group", + "log_user_group_delete": "Delete '{}' group", + "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", + "log_user_permission_add": "Update '{}' permission", "log_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_migrations_migrate_backward": "Migrate backward", @@ -370,11 +390,23 @@ "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", + "permission_already_clear": "Permission '{permission:s}' already clear for app {app:s}", + "permission_already_exist": "Permission '{permission:s}' for app {app:s} already exist", + "permission_created": "Permission '{permission:s}' for app {app:s} created", + "premission_creation_failled": "Permission creation failed", + "permission_deleted": "Permission '{permission:s}' for app {app:s} deleted", + "permission_deletion_failed": "Permission '{permission:s}' for app {app:s} deletion failed", + "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", + "permission_name_not_valid": "Permission name '{permission:s}' not valid", + "permission_update_failed": "Permission update failed", + "permission_updated": "Permission '{permission:s}' for app {app:s} updated", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_available": "Port {port:d} is available", "port_unavailable": "Port {port:d} is not available", "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create' or the admin interface.", + "remove_main_permission_not_allowed": "Removing the main permission is not allowed", + "remove_user_of_group_not_allowed": "You are not allowed to remove the user {user:s} in the group {group:s}", "restore_action_required": "You must specify something to restore", "restore_already_installed_app": "An app is already installed with the id '{app:s}'", "restore_app_failed": "Unable to restore the app '{app:s}'", @@ -459,6 +491,7 @@ "ssowat_conf_updated": "The SSOwat configuration has been updated", "ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", + "system_groupname_exists": "Groupname already exists in the system group", "system_upgraded": "The system has been upgraded", "system_username_exists": "Username already exists in the system users", "unbackup_app": "App '{app:s}' will not be saved", @@ -474,12 +507,14 @@ "upnp_disabled": "UPnP has been disabled", "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", + "user_alread_in_group": "User {user:} already in group {group:s}", "user_created": "The user has been created", "user_creation_failed": "Unable to create user", "user_deleted": "The user has been deleted", "user_deletion_failed": "Unable to delete user", "user_home_creation_failed": "Unable to create user home folder", "user_info_failed": "Unable to retrieve user information", + "user_not_in_group": "User {user:s} not in group {group:s}", "user_unknown": "Unknown user: {user:s}", "user_update_failed": "Unable to update user", "user_updated": "The user has been updated", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index a38f0b4c5..b4790926b 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -26,6 +26,7 @@ import os import re import pwd +import grp import json import crypt import random @@ -123,7 +124,8 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas # Validate uniqueness of username and mail in LDAP auth.validate_uniqueness({ 'uid': username, - 'mail': mail + 'mail': mail, + 'cn': username }) # Validate uniqueness of username in system users @@ -150,7 +152,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas # Get random UID/GID all_uid = {x.pw_uid for x in pwd.getpwall()} - all_gid = {x.pw_gid for x in pwd.getpwall()} + all_gid = {x.gr_gid for x in grp.getgrall()} uid_guid_found = False while not uid_guid_found: @@ -160,7 +162,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas # Adapt values for LDAP fullname = '%s %s' % (firstname, lastname) attr_dict = { - 'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount'], + 'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh'], 'givenName': firstname, 'sn': lastname, 'displayName': fullname, @@ -201,25 +203,26 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas # Invalidate passwd to take user creation into account subprocess.call(['nscd', '-i', 'passwd']) - # Update SFTP user group - memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid'] - memberlist.append(username) - if auth.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}): - try: - # Attempt to create user home folder - subprocess.check_call( - ['su', '-', username, '-c', "''"]) - except subprocess.CalledProcessError: - if not os.path.isdir('/home/{0}'.format(username)): - logger.warning(m18n.n('user_home_creation_failed'), - exc_info=1) - app_ssowatconf(auth) - # TODO: Send a welcome mail to user - logger.success(m18n.n('user_created')) - hook_callback('post_user_create', - args=[username, mail, password, firstname, lastname]) + try: + # Attempt to create user home folder + subprocess.check_call( + ['su', '-', username, '-c', "''"]) + except subprocess.CalledProcessError: + if not os.path.isdir('/home/{0}'.format(username)): + logger.warning(m18n.n('user_home_creation_failed'), + exc_info=1) + app_ssowatconf(auth) + # TODO: Send a welcome mail to user + logger.success(m18n.n('user_created')) + # Create group for user and add to group 'ALL' + user_group_add(auth, groupname=username, gid=uid) + user_group_update(auth, groupname=username, add_user=username, force=True) + user_group_update(auth, 'ALL', add_user=username, force=True) - return {'fullname': fullname, 'username': username, 'mail': mail} + hook_callback('post_user_create', + args=[username, mail, password, firstname, lastname]) + + return {'fullname': fullname, 'username': username, 'mail': mail} raise YunohostError('user_creation_failed') @@ -242,19 +245,24 @@ def user_delete(operation_logger, auth, username, purge=False): # Invalidate passwd to take user deletion into account subprocess.call(['nscd', '-i', 'passwd']) - # Update SFTP user group - memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid'] - try: - memberlist.remove(username) - except: - pass - if auth.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}): - if purge: - subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) - subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)]) + if purge: + subprocess.call(['rm', '-rf', '/home/{0}'.format(username)]) else: raise YunohostError('user_deletion_failed') + user_group_delete(auth, username, force=True) + + group_list = auth.search('ou=groups,dc=yunohost,dc=org', + '(&(objectclass=groupOfNamesYnh)(memberUid=%s))' + % username, ['cn']) + for group in group_list: + user_list = auth.search('ou=groups,dc=yunohost,dc=org', + 'cn=' + group['cn'][0], + ['memberUid'])[0] + user_list['memberUid'].remove(username) + if not auth.update('cn=%s,ou=groups' % group['cn'][0], user_list): + raise YunohostError('group_update_failed') + app_ssowatconf(auth) hook_callback('post_user_delete', args=[username, purge]) @@ -462,6 +470,278 @@ def user_info(auth, username): else: raise YunohostError('user_info_failed') +# +# Group subcategory +# +# +def user_group_list(auth, fields=None): + """ + List users + + Keyword argument: + filter -- LDAP filter used to search + offset -- Starting number for user fetching + limit -- Maximum number of user fetched + fields -- fields to fetch + + """ + group_attr = { + 'cn' : 'groupname', + 'member' : 'members', + 'permission' : 'permission' + } + attrs = ['cn'] + groups = {} + + if fields: + keys = group_attr.keys() + for attr in fields: + if attr in keys: + attrs.append(attr) + else: + raise MoulinetteError(errno.EINVAL, + m18n.n('field_invalid', attr)) + else: + attrs = ['cn', 'member'] + + result = auth.search('ou=groups,dc=yunohost,dc=org', + '(objectclass=groupOfNamesYnh)', + attrs) + + for group in result: + # The group "admins" should be hidden for the user + if group_attr['cn'] == "admins": + continue + entry = {} + for attr, values in group.items(): + if values: + if attr == "member": + entry[group_attr[attr]] = [] + for v in values: + entry[group_attr[attr]].append(v.split("=")[1].split(",")[0]) + elif attr == "permission": + entry[group_attr[attr]] = {} + for v in values: + permission = v.split("=")[1].split(",")[0].split(".")[0] + pType = v.split("=")[1].split(",")[0].split(".")[1] + if permission in entry[group_attr[attr]]: + entry[group_attr[attr]][permission].append(pType) + else: + entry[group_attr[attr]][permission] = [pType] + else: + entry[group_attr[attr]] = values[0] + + groupname = entry[group_attr['cn']] + groups[groupname] = entry + return {'groups' : groups} + + +@is_unit_operation([('groupname', 'user')]) +def user_group_add(operation_logger, auth, groupname,gid=None): + """ + Create group + + Keyword argument: + groupname -- Must be unique + + """ + from yunohost.app import app_ssowatconf + from yunohost.permission import _permission_sync_to_user + + operation_logger.start() + + # Validate uniqueness of groupname in LDAP + conflict = auth.get_conflict({ + 'cn': groupname + }, base_dn='ou=groups,dc=yunohost,dc=org') + if conflict: + raise MoulinetteError(errno.EEXIST, m18n.n('group_name_already_exist', name=groupname)) + + # Validate uniqueness of groupname in system group + all_existing_groupnames = {x.gr_name for x in grp.getgrall()} + if groupname in all_existing_groupnames: + raise MoulinetteError(errno.EEXIST, m18n.n('system_groupname_exists')) + + if not gid: + # Get random GID + all_gid = {x.gr_gid for x in grp.getgrall()} + + uid_guid_found = False + while not uid_guid_found: + gid = str(random.randint(200, 99999)) + uid_guid_found = gid not in all_gid + + attr_dict = { + 'objectClass': ['top', 'groupOfNamesYnh', 'posixGroup'], + 'cn': groupname, + 'gidNumber': gid, + } + + if auth.add('cn=%s,ou=groups' % groupname, attr_dict): + _permission_sync_to_user(auth) + app_ssowatconf(auth) + logger.success(m18n.n('group_created')) + return {'name': groupname} + + raise MoulinetteError(169, m18n.n('group_creation_failed')) + + +@is_unit_operation([('groupname', 'user')]) +def user_group_delete(operation_logger, auth, groupname, force=False): + """ + Delete user + + Keyword argument: + groupname -- Groupname to delete + + """ + from yunohost.app import app_ssowatconf + from yunohost.permission import _permission_sync_to_user + + if not force and (groupname == 'ALL' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): + raise MoulinetteError(errno.EPERM, m18n.n('group_deletion_not_allowed', user=groupname)) + + operation_logger.start() + if not auth.remove('cn=%s,ou=groups' % groupname): + raise MoulinetteError(169, m18n.n('group_deletion_failed')) + + _permission_sync_to_user(auth) + app_ssowatconf(auth) + logger.success(m18n.n('group_deleted')) + + +@is_unit_operation([('groupname', 'user')]) +def user_group_update(operation_logger, auth, groupname, add_user=None, remove_user=None, force=False): + """ + Update user informations + + Keyword argument: + groupname -- Groupname to update + add_user -- User to add in group + remove_user -- User to remove in group + + """ + + from yunohost.app import app_ssowatconf + from yunohost.permission import _permission_sync_to_user + + attrs_to_fetch = ['member'] + + if (groupname == 'ALL' or groupname == 'admins') and not force: + raise MoulinetteError(errno.EINVAL, m18n.n('edit_group_not_allowed', group=groupname)) + + # Populate group informations + result = auth.search(base='ou=groups,dc=yunohost,dc=org', + filter='cn=' + groupname, attrs=attrs_to_fetch) + if not result: + raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=groupname)) + group = result[0] + + new_group_list = {'member': set(), 'memberUid': set()} + if 'member' in group: + new_group_list['member'] = set(group['member']) + else: + group['member'] = [] + + user_l = user_list(auth, ['uid'])['users'] + + if add_user: + if not isinstance(add_user, list): + add_user = [add_user] + for user in add_user: + if not user in user_l: + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=user)) + userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" + if userDN in group['member']: + logger.warning(m18n.n('user_alread_in_group', user=user, group=groupname)) + new_group_list['member'].add(userDN) + + if remove_user: + if not isinstance(remove_user, list): + remove_user = [remove_user] + for user in remove_user: + userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" + if user == groupname: + raise MoulinetteError(errno.EINVAL, + m18n.n('remove_user_of_group_not_allowed', user=user, group=groupname)) + if 'member' in group and userDN in group['member']: + new_group_list['member'].remove(userDN) + else: + logger.warning(m18n.n('user_not_in_group', user=user, group=groupname)) + + # Sychronise memberUid with member (to keep the posix group structure) + # In posixgroup the main group of each user is only written in the gid number of the user + for member in new_group_list['member']: + member_Uid = member.split("=")[1].split(",")[0] + # Don't add main user in the group. + # Note that in the Unix system the main user of the group is linked by the gid in the user attribute. + # So the main user need to be not in the memberUid list of his own group. + if member_Uid != groupname: + new_group_list['memberUid'].add(member_Uid) + + operation_logger.start() + + if new_group_list['member'] != set(group['member']): + if not auth.update('cn=%s,ou=groups' % groupname, new_group_list): + raise MoulinetteError(169, m18n.n('group_update_failed')) + + _permission_sync_to_user(auth) + logger.success(m18n.n('group_updated')) + app_ssowatconf(auth) + return user_group_info(auth, groupname) + + +def user_group_info(auth, groupname): + """ + Get user informations + + Keyword argument: + groupname -- Groupname to get informations + + """ + group_attrs = [ + 'cn', 'member', 'permission' + ] + result = auth.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname, group_attrs) + + if not result: + raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=groupname)) + else: + group = result[0] + + result_dict = { + 'groupname': group['cn'][0], + 'member': None + } + if 'member' in group: + result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']} + return result_dict + +# +# Permission subcategory +# +# +import yunohost.permission + +def user_permission_list(auth, app=None, permission=None, username=None, group=None): + return yunohost.permission.user_permission_list(auth, app, permission, username, group) + +@is_unit_operation([('app', 'user')]) +def user_permission_add(operation_logger, auth, app, permission="main", username=None, group=None): + return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, + add_username=username, add_group=group, + del_username=None, del_group=None) + +@is_unit_operation([('app', 'user')]) +def user_permission_remove(operation_logger, auth, app, permission="main", username=None, group=None): + return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, + add_username=None, add_group=None, + del_username=username, del_group=group) + +@is_unit_operation([('app', 'user')]) +def user_permission_clear(operation_logger, auth, app, permission=None): + return yunohost.permission.user_permission_clear(operation_logger, auth, app, permission) + # # SSH subcategory # From fbaddd900283cb57cd4da504e704278d31e84ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 22:53:31 +0100 Subject: [PATCH 092/721] Implement permission management --- data/other/ldap_scheme.yml | 8 +- locales/en.json | 3 + src/yunohost/permission.py | 490 +++++++++++++++++++++++++++++++++++++ src/yunohost/user.py | 8 +- 4 files changed, 501 insertions(+), 8 deletions(-) create mode 100644 src/yunohost/permission.py diff --git a/data/other/ldap_scheme.yml b/data/other/ldap_scheme.yml index d30c4915c..11504bbe8 100644 --- a/data/other/ldap_scheme.yml +++ b/data/other/ldap_scheme.yml @@ -51,8 +51,8 @@ children: objectClass: - posixGroup - top - cn=ALL,ou=groups: - cn: ALL + cn=all_users,ou=groups: + cn: all_users gidNumber: "4002" objectClass: - posixGroup @@ -66,7 +66,7 @@ depends_children: - posixGroup - permissionYnh groupPermission: - - "cn=ALL,ou=groups,dc=yunohost,dc=org" + - "cn=all_users,ou=groups,dc=yunohost,dc=org" cn=main.metronome,ou=permission: cn: main.metronome gidNumber: "5002" @@ -74,4 +74,4 @@ depends_children: - posixGroup - permissionYnh groupPermission: - - "cn=ALL,ou=groups,dc=yunohost,dc=org" + - "cn=all_users,ou=groups,dc=yunohost,dc=org" diff --git a/locales/en.json b/locales/en.json index 5b78cdcd7..51cb01b0b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -271,6 +271,7 @@ "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", "log_user_permission_add": "Update '{}' permission", + "log_user_permission_remove": "Update '{}' permisson", "log_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_migrations_migrate_backward": "Migrate backward", @@ -357,6 +358,7 @@ "mysql_db_creation_failed": "MySQL database creation failed", "mysql_db_init_failed": "MySQL database init failed", "mysql_db_initialized": "The MySQL database has been initialized", + "need_define_permission_before": "You need to redefine the permission by 'yunohost user permission add -u USER' before to remove an allowed group", "network_check_mx_ko": "DNS MX record is not set", "network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network", "network_check_smtp_ok": "Outbound mail (SMTP port 25) is not blocked", @@ -400,6 +402,7 @@ "permission_name_not_valid": "Permission name '{permission:s}' not valid", "permission_update_failed": "Permission update failed", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", + "permission_update_nothing_to_do": "Permission update nothing to do", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_available": "Port {port:d} is available", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py new file mode 100644 index 000000000..df53e9b6b --- /dev/null +++ b/src/yunohost/permission.py @@ -0,0 +1,490 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2014 YUNOHOST.ORG + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses + +""" + +""" yunohost_permission.py + + Manage permissions +""" + +import errno +import grp +import random + +from moulinette import m18n +from moulinette.core import MoulinetteError +from moulinette.utils.log import getActionLogger +from yunohost.user import user_list, user_group_list +from yunohost.app import app_ssowatconf +from yunohost.log import is_unit_operation + +logger = getActionLogger('yunohost.user') + +def user_permission_list(auth, app=None, permission=None, username=None, group=None): + """ + List permission for specific application + + Keyword argument: + app -- an application OR sftp, xmpp (metronome), mail + permission -- name of the permission ("main" by default) + username -- Username to get informations + group -- Groupname to get informations + + """ + + user_l = user_list(auth, ['uid'])['users'] + + permission_attrs = [ + 'cn', + 'groupPermission', + 'inheritPermission', + 'URL', + ] + + # Normally app is alway defined but it should be possible to set it + if app and not isinstance(app, list): + app = [app] + if permission and not isinstance(permission, list): + permission = [permission] + if not isinstance(group, list): + group = [group] + if isinstance(username, list): + group.extend(username) + else: + group.append(username) + group = filter(None, group) + + permissions = {} + + result = auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', permission_attrs) + + for res in result: + permission_name = res['cn'][0].split('.')[0] + try: + app_name = res['cn'][0].split('.')[1] + except: + logger.warning(m18n.n('permission_name_not_valid', permission=per)) + group_name = [] + if 'groupPermission' in res: + for g in res['groupPermission']: + group_name.append(g.split("=")[1].split(",")[0]) + user_name = [] + if 'inheritPermission' in res: + for u in res['inheritPermission']: + user_name.append(u.split("=")[1].split(",")[0]) + + # Don't show the result if the user diffined a specific permission, user or group + if app and not app_name in app: + continue + if permission and not permission_name in permission: + continue + if group and not set(group) & set(group_name): + continue + + if not app_name in permissions: + permissions[app_name] = {} + + permissions[app_name][permission_name] = {'allowed_users':[], 'allowed_groups':[]} + for g in group_name: + permissions[app_name][permission_name]['allowed_groups'].append(g) + for u in user_name: + permissions[app_name][permission_name]['allowed_users'].append(u) + if 'URL' in res: + permissions[app_name][permission_name]['URL'] = [] + for u in res['URL']: + permissions[app_name][permission_name]['URL'].append(u) + + return {'permissions': permissions} + + +def user_permission_update(operation_logger, auth, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None): + """ + Allow or Disallow a user or group to a permission for a specific application + + Keyword argument: + app -- an application OR sftp, xmpp (metronome), mail + permission -- name of the permission ("main" by default) + add_username -- Username to allow + add_group -- Groupname to allow + del_username -- Username to disallow + del_group -- Groupname to disallow + + """ + from yunohost.hook import hook_callback + from yunohost.user import user_group_list + + if permission: + if not isinstance(permission, list): + permission = [permission] + else: + permission = ["main"] + + if add_group: + if not isinstance(add_group, list): + add_group = [add_group] + else: + add_group = [] + + if add_username: + if not isinstance(add_username, list): + add_username = [add_username] + else: + add_username = [] + + if del_group: + if not isinstance(del_group, list): + del_group = [del_group] + else: + del_group = [] + + if del_username: + if not isinstance(del_username, list): + del_username = [del_username] + else: + del_username = [] + + # Validate that the group exist + for g in add_group: + if not g in user_group_list(auth, ['cn'])['groups']: + raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=g)) + for u in add_username: + if not u in user_list(auth, ['uid'])['users']: + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=u)) + for g in del_group: + if not g in user_group_list(auth, ['cn'])['groups']: + raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=g)) + for u in del_username: + if not u in user_list(auth, ['uid'])['users']: + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=u)) + + # Merge user and group (note that we consider all user as a group) + add_group.extend(add_username) + del_group.extend(del_username) + + if 'all_users' in add_group or 'all_users' in del_group: + raise MoulinetteError(errno.EINVAL, m18n.n('edit_permission_with_group_all_users_not_allowed')) + + # Populate permission informations + permission_attrs = [ + 'cn', + 'groupPermission', + ] + result = auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', permission_attrs) + result = {p['cn'][0]: p for p in result} + + new_per_dict = {} + + for a in app: + for per in permission: + permission_name = per + '.' + a + if not permission_name in result: + raise MoulinetteError(errno.EINVAL, m18n.n('permission_not_found', permission=per, app=a)) + new_per_dict[permission_name] = set() + if 'groupPermission' in result[permission_name]: + new_per_dict[permission_name] = set(result[permission_name]['groupPermission']) + + for g in del_group: + if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]: + raise MoulinetteError(errno.EINVAL, m18n.n('need_define_permission_before')) + group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' + if not group_name in new_per_dict[permission_name]: + logger.warning(m18n.n('group_alread_disallowed', permission=per, app=a, group=g)) + else: + new_per_dict[permission_name].remove(group_name) + + if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]: + new_per_dict[permission_name].remove('cn=all_users,ou=groups,dc=yunohost,dc=org') + for g in add_group: + group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' + if group_name in new_per_dict[permission_name]: + logger.warning(m18n.n('group_alread_allowed', permission=per, app=a, group=g)) + else: + new_per_dict[permission_name].add(group_name) + + operation_logger.start() + + for per, val in new_per_dict.items(): + # Don't update LDAP if we update exactly the same values + if val == set(result[per]['groupPermission'] if 'groupPermission' in result[per] else []): + continue + if auth.update('cn=%s,ou=permission' % per, {'groupPermission': val}): + p = per.split('.') + logger.success(m18n.n('permission_updated', permission=p[0], app=p[1])) + else: + raise MoulinetteError(169, m18n.n('permission_update_failed')) + + _permission_sync_to_user(auth) + + for a in app: + allowed_users = set() + disallowed_users = set() + group_list = user_group_list(auth, ['member'])['groups'] + + for g in add_group: + if 'members' in group_list[g]: + allowed_users.union(group_list[g]['members']) + for g in del_group: + if 'members' in group_list[g]: + disallowed_users.union(group_list[g]['members']) + + allowed_users = ','.join(allowed_users) + disallowed_users = ','.join(disallowed_users) + if add_group: + hook_callback('post_app_addaccess', args=[app, allowed_users]) + if del_group: + hook_callback('post_app_removeaccess', args=[app, disallowed_users]) + + app_ssowatconf(auth) + return user_permission_list(auth, app, permission) + + +def user_permission_clear(operation_logger, auth, app=[], permission=None): + """ + Reset the permission for a specific application + + Keyword argument: + app -- an application OR sftp, xmpp (metronome), mail + permission -- name of the permission ("main" by default) + username -- Username to get informations (all by default) + group -- Groupname to get informations (all by default) + + """ + from yunohost.hook import hook_callback + + if permission: + if not isinstance(permission, list): + permission = [permission] + else: + permission = ["main"] + + default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']} + + # Populate permission informations + permission_attrs = [ + 'cn', + 'groupPermission', + ] + result = auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', permission_attrs) + result = {p['cn'][0]: p for p in result} + + for a in app: + for per in permission: + permission_name = per + '.' + a + if not permission_name in result: + raise MoulinetteError(errno.EINVAL, m18n.n('permission_not_found', permission=per, app=a)) + if 'groupPermission' in result[permission_name] and 'cn=all_users,ou=groups,dc=yunohost,dc=org' in result[permission_name]['groupPermission']: + logger.warning(m18n.n('permission_already_clear', permission=per, app=a)) + continue + if auth.update('cn=%s,ou=permission' % permission_name, default_permission): + logger.success(m18n.n('permission_updated', permission=per, app=a)) + else: + raise MoulinetteError(169, m18n.n('permission_update_failed')) + + _permission_sync_to_user(auth) + + for a in app: + permission_name = 'main.' + a + result = auth.search('ou=permission,dc=yunohost,dc=org', + filter='cn=' + permission_name, attrs=['inheritPermission']) + if result: + allowed_users = result[0]['inheritPermission'] + new_user_list = ','.join(allowed_users) + hook_callback('post_app_removeaccess', args=[app, new_user_list]) + + app_ssowatconf(auth) + return user_permission_list(auth, app, permission) + + +@is_unit_operation(['permission','app']) +def permission_add(operation_logger, auth, app, permission, url=None): + """ + Create a new permission for a specific application + + Keyword argument: + app -- an application OR sftp, xmpp (metronome), mail + permission -- name of the permission ("main" by default) + url -- list of url to specify for the permission + + """ + from yunohost.domain import _normalize_domain_path + + # Validate uniqueness of permission in LDAP + permission_name = str(permission + '.' + app) # str(...) Fix encoding issue + conflict = auth.get_conflict({ + 'cn': permission_name + }, base_dn='ou=permission,dc=yunohost,dc=org') + if conflict: + raise MoulinetteError(errno.EEXIST, m18n.n('permission_already_exist', permission=permission, app=app)) + + # Get random GID + all_gid = {x.gr_gid for x in grp.getgrall()} + + uid_guid_found = False + while not uid_guid_found: + gid = str(random.randint(200, 99999)) + uid_guid_found = gid not in all_gid + + attr_dict = { + 'objectClass': ['top', 'permissionYnh', 'posixGroup'], + 'cn': permission_name, + 'gidNumber': gid, + 'groupPermission': 'cn=all_users,ou=groups,dc=yunohost,dc=org' + } + + if url: + attr_dict['URL'] = [] + for u in url: + domain = u[:u.index('/')] + path = u[u.index('/'):] + domain, path = _normalize_domain_path(domain, path) + attr_dict['URL'].append(domain + path) + + operation_logger.start() + if auth.add('cn=%s,ou=permission' % permission_name, attr_dict): + _permission_sync_to_user(auth) + logger.success(m18n.n('permission_created', permission=permission, app=app)) + return user_permission_list(auth, app, permission) + + raise MoulinetteError(169, m18n.n('premission_creation_failled')) + + +@is_unit_operation(['permission','app']) +def permission_update(operation_logger, auth, app, permission, add_url=None, remove_url=None): + """ + Update a permission for a specific application + + Keyword argument: + app -- an application OR sftp, xmpp (metronome), mail + permission -- name of the permission ("main" by default) + add_url -- Add a new url for a permission + remove_url -- Remove a url for a permission + + """ + from yunohost.domain import _normalize_domain_path + + permission_name = str(permission + '.' + app) # str(...) Fix encoding issue + + # Populate permission informations + result = auth.search(base='ou=permission,dc=yunohost,dc=org', + filter='cn=' + permission_name, attrs=['URL']) + if not result: + raise MoulinetteError(errno.EINVAL, m18n.n('permission_not_found', permission=permission, app=app)) + permission_obj = result[0] + + if not 'URL' in permission_obj: + permission_obj['URL'] = [] + + url = set(permission_obj['URL']) + + if add_url: + for u in add_url: + domain = u[:u.index('/')] + path = u[u.index('/'):] + domain, path = _normalize_domain_path(domain, path) + url.add(domain + path) + if remove_url: + for u in remove_url: + domain = u[:u.index('/')] + path = u[u.index('/'):] + domain, path = _normalize_domain_path(domain, path) + url.discard(domain + path) + + if url == set(permission_obj['URL']): + logger.warning(m18n.n('permission_update_nothing_to_do')) + return user_permission_list(auth, app, permission) + + operation_logger.start() + if auth.update('cn=%s,ou=permission' % permission_name, {'cn':permission_name, 'URL': url}): + _permission_sync_to_user(auth) + logger.success(m18n.n('permission_updated', permission=permission, app=app)) + return user_permission_list(auth, app, permission) + + raise MoulinetteError(169, m18n.n('premission_update_failled')) + + +@is_unit_operation(['permission','app']) +def permission_remove(operation_logger, auth, app, permission, force=False): + """ + Remove a permission for a specific application + + Keyword argument: + app -- an application OR sftp, xmpp (metronome), mail + permission -- name of the permission ("main" by default) + + """ + + if permission == "main" and not force: + raise MoulinetteError(errno.EPERM, m18n.n('remove_main_permission_not_allowed')) + + operation_logger.start() + if not auth.remove('cn=%s,ou=permission' % str(permission + '.' + app)): + raise MoulinetteError(169, m18n.n('permission_deletion_failed', permission=permission, app=app)) + _permission_sync_to_user(auth) + logger.success(m18n.n('permission_deleted', permission=permission, app=app)) + + +def _permission_sync_to_user(auth): + """ + Sychronise the inheritPermission attribut in the permission object from the user<->group link and the group<->permission link + """ + import os + + permission_attrs = [ + 'cn', + 'member', + 'permission', + ] + group_info = auth.search('ou=groups,dc=yunohost,dc=org', + '(objectclass=groupOfNamesYnh)', permission_attrs) + user_permission={} + + for group in group_info: + if 'permission' not in group: + continue + if not 'member' in group: + continue + for permission in group['permission']: + permission = permission.split("=")[1].split(",")[0] + if not permission in user_permission: + user_permission[permission] = set() + for member in group['member']: + user_permission[permission].add(member) + + for per in auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', ['cn', 'inheritPermission']): + if per['cn'][0] in user_permission: + val = set(user_permission[per['cn'][0]]) + else: + # If the new value and the old value à empty nothing to do + if not 'inheritPermission' in per: + continue + val = set() + if 'inheritPermission' in per and val == set(per['inheritPermission']): + continue + uid_val = [v.split("=")[1].split(",")[0] for v in val] + inheritPermission = {'inheritPermission': val, 'memberUid': uid_val} + if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): + raise MoulinetteError(169, m18n.n('permission_update_failed')) + + # Reload unscd because if not the group is not updated in the system from LDAP + os.system('systemctl restart unscd') diff --git a/src/yunohost/user.py b/src/yunohost/user.py index b4790926b..a2ff5b5a9 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -214,10 +214,10 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas app_ssowatconf(auth) # TODO: Send a welcome mail to user logger.success(m18n.n('user_created')) - # Create group for user and add to group 'ALL' + # Create group for user and add to group 'all_users' user_group_add(auth, groupname=username, gid=uid) user_group_update(auth, groupname=username, add_user=username, force=True) - user_group_update(auth, 'ALL', add_user=username, force=True) + user_group_update(auth, 'all_users', add_user=username, force=True) hook_callback('post_user_create', args=[username, mail, password, firstname, lastname]) @@ -598,7 +598,7 @@ def user_group_delete(operation_logger, auth, groupname, force=False): from yunohost.app import app_ssowatconf from yunohost.permission import _permission_sync_to_user - if not force and (groupname == 'ALL' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): + if not force and (groupname == 'all_users' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): raise MoulinetteError(errno.EPERM, m18n.n('group_deletion_not_allowed', user=groupname)) operation_logger.start() @@ -627,7 +627,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u attrs_to_fetch = ['member'] - if (groupname == 'ALL' or groupname == 'admins') and not force: + if (groupname == 'all_users' or groupname == 'admins') and not force: raise MoulinetteError(errno.EINVAL, m18n.n('edit_group_not_allowed', group=groupname)) # Populate group informations From c1dc117863b74b91e641026792f6833aa3f64077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 23:01:48 +0100 Subject: [PATCH 093/721] Implement permission for app --- data/actionsmap/yunohost.yml | 15 +- src/yunohost/app.py | 199 ++++++++------------------- src/yunohost/domain.py | 2 +- src/yunohost/tests/test_changeurl.py | 2 +- src/yunohost/user.py | 4 +- 5 files changed, 70 insertions(+), 152 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index afd2ed3b3..360b0780a 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -338,7 +338,7 @@ user: arguments: app: help: Application to manage the permission - nargs: "*" + nargs: "+" -p: full: --permission help: Name of permission (main by default) @@ -368,7 +368,7 @@ user: arguments: app: help: Application to manage the permission - nargs: "*" + nargs: "+" -p: full: --permission help: Name of permission (main by default) @@ -398,7 +398,7 @@ user: arguments: app: help: Application to manage the permission - nargs: "*" + nargs: "+" -p: full: --permission help: Name of permission (main by default) @@ -711,6 +711,9 @@ app: map: action_help: List apps by domain api: GET /appsmap + configuration: + authenticate: all + authenticator: ldap-anonymous arguments: -a: full: --app @@ -731,7 +734,6 @@ app: api: POST /apps configuration: authenticate: all - authenticator: ldap-anonymous arguments: app: help: Name, local path or git URL of the app to install @@ -756,7 +758,6 @@ app: api: DELETE /apps/ configuration: authenticate: all - authenticator: ldap-anonymous arguments: app: help: App(s) to delete @@ -785,7 +786,6 @@ app: api: PUT /apps//changeurl configuration: authenticate: all - authenticator: ldap-anonymous arguments: app: help: Target app instance name @@ -931,7 +931,6 @@ app: api: PUT /access configuration: authenticate: all - authenticator: ldap-anonymous arguments: apps: nargs: "+" @@ -945,7 +944,6 @@ app: api: DELETE /access configuration: authenticate: all - authenticator: ldap-anonymous arguments: apps: nargs: "+" @@ -959,7 +957,6 @@ app: api: POST /access configuration: authenticate: all - authenticator: ldap-anonymous arguments: apps: nargs: "+" diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3925a86db..72755a5c5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -381,7 +381,7 @@ def app_info(app, show_status=False, raw=False): return info -def app_map(app=None, raw=False, user=None): +def app_map(auth, app=None, raw=False, user=None): """ List apps by domain @@ -391,6 +391,8 @@ def app_map(app=None, raw=False, user=None): app -- Specific app to map """ + from yunohost.permission import user_permission_list + apps = [] result = {} @@ -410,11 +412,9 @@ def app_map(app=None, raw=False, user=None): if 'no_sso' in app_settings: # I don't think we need to check for the value here continue if user is not None: - if ('mode' not in app_settings - or ('mode' in app_settings - and app_settings['mode'] == 'private')) \ - and 'allowed_users' in app_settings \ - and user not in app_settings['allowed_users'].split(','): + if not auth.search(base='ou=permission,dc=yunohost,dc=org', + filter='(&(objectclass=permissionYnh)(cn=main.%s)(inheritPermission=uid=%s,ou=users,dc=yunohost,dc=org))' % (app_id, user), + attrs=['cn']): continue domain = app_settings['domain'] @@ -446,6 +446,7 @@ def app_change_url(operation_logger, auth, app, domain, path): """ from yunohost.hook import hook_exec, hook_callback from yunohost.domain import _normalize_domain_path, _get_conflicting_apps + from yunohost.permission import permission_update installed = _is_installed(app) if not installed: @@ -535,6 +536,8 @@ def app_change_url(operation_logger, auth, app, domain, path): app_setting(app, 'domain', value=domain) app_setting(app, 'path', value=path) + permission_update(auth, app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path]) + app_ssowatconf(auth) # avoid common mistakes @@ -707,6 +710,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on """ from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger + from yunohost.permission import permission_add, permission_update # Fetch or extract sources try: @@ -769,7 +773,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on _check_manifest_requirements(manifest, app_id) # Check if app can be forked - instance_number = _installed_instance_number(app_id, last=True) + 1 + instance_number = _installed_instance_number(auth, app_id, last=True) + 1 if instance_number > 1: if 'multi_instance' not in manifest or not is_true(manifest['multi_instance']): raise YunohostError('app_already_installed', app=app_id) @@ -828,6 +832,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)): os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) + # Create permission before the install (useful if the install script redefine the permission) + permission_add(auth, app=app_instance_name, permission="main") + # Execute the app install script install_retcode = 1 try: @@ -896,6 +903,13 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on os.system('chown -R root: %s' % app_setting_path) os.system('chown -R admin: %s/scripts' % app_setting_path) + # Add path in permission if it's defined in the app install script + app_settings = _get_app_settings(app_instance_name) + domain = app_settings['domain'] + path = app_settings.get('path', '/') + if domain and path: + permission_update(auth, app_instance_name, permission="main", add_url=[domain+path]) + app_ssowatconf(auth) logger.success(m18n.n('installation_complete')) @@ -913,6 +927,7 @@ def app_remove(operation_logger, auth, app): """ from yunohost.hook import hook_exec, hook_remove, hook_callback + from yunohost.permission import permission_remove if not _is_installed(app): raise YunohostError('app_not_installed', app=app) @@ -954,10 +969,18 @@ def app_remove(operation_logger, auth, app): shutil.rmtree(app_setting_path) shutil.rmtree('/tmp/yunohost_remove') hook_remove(app) + + # Remove all permission in LDAP + result = auth.search(base='ou=permission,dc=yunohost,dc=org', + filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app, attrs=['cn']) + permission_list = [p['cn'][0] for p in result] + for l in permission_list: + permission_remove(auth, app, l.split('.')[0], force=True) + app_ssowatconf(auth) - -def app_addaccess(auth, apps, users=[]): +@is_unit_operation(['permission','app']) +def app_addaccess(operation_logger, auth, apps, users=[]): """ Grant access right to users (everyone by default) @@ -966,64 +989,17 @@ def app_addaccess(auth, apps, users=[]): apps """ - from yunohost.user import user_list, user_info - from yunohost.hook import hook_callback + from yunohost.permission import user_permission_update - result = {} + permission = user_permission_update(operation_logger, auth, app=apps, permission="main", add_username=users) - if not users: - users = user_list(auth)['users'].keys() - elif not isinstance(users, list): - users = [users, ] - if not isinstance(apps, list): - apps = [apps, ] - - for app in apps: - - app_settings = _get_app_settings(app) - if not app_settings: - continue - - if 'mode' not in app_settings: - app_setting(app, 'mode', 'private') - app_settings['mode'] = 'private' - - if app_settings['mode'] == 'private': - - # Start register change on system - related_to = [('app', app)] - operation_logger = OperationLogger('app_addaccess', related_to) - operation_logger.start() - - allowed_users = set() - if 'allowed_users' in app_settings and app_settings['allowed_users']: - allowed_users = set(app_settings['allowed_users'].split(',')) - - for allowed_user in users: - if allowed_user not in allowed_users: - try: - user_info(auth, allowed_user) - except YunohostError: - logger.warning(m18n.n('user_unknown', user=allowed_user)) - continue - allowed_users.add(allowed_user) - operation_logger.related_to.append(('user', allowed_user)) - - operation_logger.flush() - new_users = ','.join(allowed_users) - app_setting(app, 'allowed_users', new_users) - hook_callback('post_app_addaccess', args=[app, new_users]) - - operation_logger.success() - - result[app] = allowed_users - - app_ssowatconf(auth) + result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} return {'allowed_users': result} -def app_removeaccess(auth, apps, users=[]): +@is_unit_operation(['permission','app']) +def app_removeaccess(operation_logger, auth, apps, users=[]): """ Revoke access right to users (everyone by default) @@ -1032,59 +1008,17 @@ def app_removeaccess(auth, apps, users=[]): apps """ - from yunohost.user import user_list - from yunohost.hook import hook_callback + from yunohost.permission import user_permission_update - result = {} + permission = user_permission_update(operation_logger, auth, app=apps, permission="main", del_username=users) - remove_all = False - if not users: - remove_all = True - elif not isinstance(users, list): - users = [users, ] - if not isinstance(apps, list): - apps = [apps, ] - - for app in apps: - app_settings = _get_app_settings(app) - if not app_settings: - continue - allowed_users = set() - - if app_settings.get('skipped_uris', '') != '/': - - # Start register change on system - related_to = [('app', app)] - operation_logger = OperationLogger('app_removeaccess', related_to) - operation_logger.start() - - if remove_all: - pass - elif 'allowed_users' in app_settings: - for allowed_user in app_settings['allowed_users'].split(','): - if allowed_user not in users: - allowed_users.add(allowed_user) - else: - for allowed_user in user_list(auth)['users'].keys(): - if allowed_user not in users: - allowed_users.add(allowed_user) - - operation_logger.related_to += [('user', x) for x in allowed_users] - operation_logger.flush() - new_users = ','.join(allowed_users) - app_setting(app, 'allowed_users', new_users) - hook_callback('post_app_removeaccess', args=[app, new_users]) - - result[app] = allowed_users - - operation_logger.success() - - app_ssowatconf(auth) + result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} return {'allowed_users': result} -def app_clearaccess(auth, apps): +@is_unit_operation(['permission','app']) +def app_clearaccess(operation_logger, auth, apps): """ Reset access rights for the app @@ -1092,32 +1026,9 @@ def app_clearaccess(auth, apps): apps """ - from yunohost.hook import hook_callback + from yunohost.permission import user_permission_clear - if not isinstance(apps, list): - apps = [apps] - - for app in apps: - app_settings = _get_app_settings(app) - if not app_settings: - continue - - # Start register change on system - related_to = [('app', app)] - operation_logger = OperationLogger('app_clearaccess', related_to) - operation_logger.start() - - if 'mode' in app_settings: - app_setting(app, 'mode', delete=True) - - if 'allowed_users' in app_settings: - app_setting(app, 'allowed_users', delete=True) - - hook_callback('post_app_clearaccess', args=[app]) - - operation_logger.success() - - app_ssowatconf(auth) + user_permission_clear(operation_logger, auth, app=apps, permission="main") def app_debug(app): @@ -1166,8 +1077,9 @@ def app_makedefault(operation_logger, auth, app, domain=None): raise YunohostError('domain_unknown') operation_logger.start() - if '/' in app_map(raw=True)[domain]: - raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, other_app=app_map(raw=True)[domain]["/"]["id"]) + if '/' in app_map(auth, raw=True)[domain]: + raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, + other_app=app_map(auth, raw=True)[domain]["/"]["id"])) try: with open('/etc/ssowat/conf.json.persistent') as json_conf: @@ -1312,7 +1224,7 @@ def app_checkurl(auth, url, app=None): domain, path = _normalize_domain_path(domain, path) - apps_map = app_map(raw=True) + apps_map = app_map(auth, raw=True) if domain not in domain_list(auth)['domains']: raise YunohostError('domain_unknown') @@ -1379,6 +1291,7 @@ def app_ssowatconf(auth): """ from yunohost.domain import domain_list, _get_maindomain from yunohost.user import user_list + from yunohost.permission import user_permission_list main_domain = _get_maindomain() domains = domain_list(auth)['domains'] @@ -1438,6 +1351,13 @@ def app_ssowatconf(auth): skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$") skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$") + permission = {} + for a in user_permission_list(auth)['permissions'].values(): + for p in a.values(): + if 'URL' in p: + for u in p['URL']: + permission[u] = p['allowed_users'] + conf_dict = { 'portal_domain': main_domain, 'portal_path': '/yunohost/sso/', @@ -1456,8 +1376,9 @@ def app_ssowatconf(auth): 'protected_regex': protected_regex, 'redirected_urls': redirected_urls, 'redirected_regex': redirected_regex, - 'users': {username: app_map(user=username) + 'users': {username: app_map(auth, user=username) for username in user_list(auth)['users'].keys()}, + 'permission': permission, } with open('/etc/ssowat/conf.json', 'w+') as f: @@ -1985,7 +1906,7 @@ def _fetch_app_from_git(app): return manifest, extracted_app_folder -def _installed_instance_number(app, last=False): +def _installed_instance_number(auth, app, last=False): """ Check if application is installed and return instance number @@ -2017,7 +1938,7 @@ def _installed_instance_number(app, last=False): else: instance_number_list = [] - instances_dict = app_map(app=app, raw=True) + instances_dict = app_map(auth, app=app, raw=True) for key, domain in instances_dict.items(): for key, path in domain.items(): instance_number_list.append(path['instance']) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 16d391168..1d1c66bb2 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -246,7 +246,7 @@ def _get_conflicting_apps(auth, domain, path): from yunohost.app import app_map # Fetch apps map - apps_map = app_map(raw=True) + apps_map = app_map(auth, raw=True) # Loop through all apps to check if path is taken by one of them conflicts = [] diff --git a/src/yunohost/tests/test_changeurl.py b/src/yunohost/tests/test_changeurl.py index e11acdb59..d37d3ed48 100644 --- a/src/yunohost/tests/test_changeurl.py +++ b/src/yunohost/tests/test_changeurl.py @@ -32,7 +32,7 @@ def install_changeurl_app(path): def check_changeurl_app(path): - appmap = app_map(raw=True) + appmap = app_map(auth, raw=True) assert path in appmap[maindomain].keys() diff --git a/src/yunohost/user.py b/src/yunohost/user.py index a2ff5b5a9..32cb6f684 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -522,8 +522,8 @@ def user_group_list(auth, fields=None): elif attr == "permission": entry[group_attr[attr]] = {} for v in values: - permission = v.split("=")[1].split(",")[0].split(".")[0] - pType = v.split("=")[1].split(",")[0].split(".")[1] + permission = v.split("=")[1].split(",")[0].split(".")[1] + pType = v.split("=")[1].split(",")[0].split(".")[0] if permission in entry[group_attr[attr]]: entry[group_attr[attr]][permission].append(pType) else: From c5c482c16c85717d620cb626128682f02947a194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 25 Nov 2018 23:02:10 +0100 Subject: [PATCH 094/721] Use permission for all services --- data/templates/dovecot/dovecot-ldap.conf | 4 ++-- data/templates/metronome/domain.tpl.cfg.lua | 2 +- data/templates/postfix/plain/ldap-accounts.cf | 2 +- data/templates/postfix/plain/ldap-aliases.cf | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/templates/dovecot/dovecot-ldap.conf b/data/templates/dovecot/dovecot-ldap.conf index 221fe85c1..c7c9785fd 100644 --- a/data/templates/dovecot/dovecot-ldap.conf +++ b/data/templates/dovecot/dovecot-ldap.conf @@ -3,7 +3,7 @@ auth_bind = yes ldap_version = 3 base = ou=users,dc=yunohost,dc=org user_attrs = uidNumber=500,gidNumber=8,mailuserquota=quota_rule=*:bytes=%$ -user_filter = (&(objectClass=inetOrgPerson)(uid=%n)) -pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)) +user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) +pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) default_pass_scheme = SSHA diff --git a/data/templates/metronome/domain.tpl.cfg.lua b/data/templates/metronome/domain.tpl.cfg.lua index 2c7fd7489..2ee9cfaae 100644 --- a/data/templates/metronome/domain.tpl.cfg.lua +++ b/data/templates/metronome/domain.tpl.cfg.lua @@ -8,7 +8,7 @@ VirtualHost "{{ domain }}" hostname = "localhost", user = { basedn = "ou=users,dc=yunohost,dc=org", - filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }}))", + filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=main.metronome,ou=permission,dc=yunohost,dc=org))", usernamefield = "mail", namefield = "cn", }, diff --git a/data/templates/postfix/plain/ldap-accounts.cf b/data/templates/postfix/plain/ldap-accounts.cf index bd3576dec..9f6f94e6d 100644 --- a/data/templates/postfix/plain/ldap-accounts.cf +++ b/data/templates/postfix/plain/ldap-accounts.cf @@ -1,5 +1,5 @@ server_host = localhost server_port = 389 search_base = dc=yunohost,dc=org -query_filter = (&(objectClass=mailAccount)(mail=%s)) +query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) result_attribute = uid diff --git a/data/templates/postfix/plain/ldap-aliases.cf b/data/templates/postfix/plain/ldap-aliases.cf index a9ef52cf9..5e7d3a6c1 100644 --- a/data/templates/postfix/plain/ldap-aliases.cf +++ b/data/templates/postfix/plain/ldap-aliases.cf @@ -1,5 +1,5 @@ server_host = localhost server_port = 389 search_base = dc=yunohost,dc=org -query_filter = (&(objectClass=mailAccount)(mail=%s)) +query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org)) result_attribute = maildrop From 05ba65b8d350945c7a56f6638da12168acc175d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 28 Nov 2018 09:58:39 +0100 Subject: [PATCH 095/721] Fix actionmap --- data/actionsmap/yunohost.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 360b0780a..2be9b2b2f 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -287,7 +287,7 @@ user: ### user_group_info() info: action_help: Get group information - api: GET /users/ + api: GET /users/groups/ configuration: authenticate: all authenticator: ldap-anonymous From ad628b7620826b56900bc3d6b4428fda54c2e596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 28 Nov 2018 08:20:55 +0100 Subject: [PATCH 096/721] Use root UID to authenticate to LDAP --- data/actionsmap/yunohost.yml | 37 +++++++++++++++++++++++++++++++-- data/templates/slapd/slapd.conf | 3 +++ src/yunohost/tools.py | 10 ++++----- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 2be9b2b2f..9676baf52 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -43,12 +43,19 @@ _global: parameters: uri: ldap://localhost:389 base_dn: dc=yunohost,dc=org - user_rdn: cn=admin + user_rdn: cn=admin,dc=yunohost,dc=org ldap-anonymous: vendor: ldap parameters: uri: ldap://localhost:389 base_dn: dc=yunohost,dc=org + as-root: + vendor: ldap + parameters: + # We can get this uri by (urllib.quote_plus('/var/run/slapd/ldapi') + uri: ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi + base_dn: dc=yunohost,dc=org + user_rdn: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth argument_auth: true arguments: -v: @@ -84,6 +91,7 @@ user: api: POST /users configuration: authenticate: all + authenticator: as-root arguments: username: help: The unique username to create @@ -142,6 +150,7 @@ user: api: DELETE /users/ configuration: authenticate: all + authenticator: as-root arguments: username: help: Username to delete @@ -157,6 +166,7 @@ user: api: PUT /users/ configuration: authenticate: all + authenticator: as-root arguments: username: help: Username to update @@ -238,6 +248,7 @@ user: api: POST /users/groups configuration: authenticate: all + authenticator: as-root arguments: groupname: help: The unique group name to add @@ -252,6 +263,7 @@ user: api: DELETE /users/groups/ configuration: authenticate: all + authenticator: as-root arguments: groupname: help: Username to delete @@ -264,6 +276,7 @@ user: api: PUT /users/groups/ configuration: authenticate: all + authenticator: as-root arguments: groupname: help: Username to update @@ -335,6 +348,7 @@ user: api: POST /users/permission/ configuration: authenticate: all + authenticator: as-root arguments: app: help: Application to manage the permission @@ -365,6 +379,7 @@ user: api: PUT /users/permission/ configuration: authenticate: all + authenticator: as-root arguments: app: help: Application to manage the permission @@ -395,6 +410,7 @@ user: api: DELETE /users/permission/ configuration: authenticate: all + authenticator: as-root arguments: app: help: Application to manage the permission @@ -414,6 +430,7 @@ user: api: POST /users/ssh/enable configuration: authenticate: all + authenticator: as-root arguments: username: help: Username of the user @@ -426,6 +443,7 @@ user: api: POST /users/ssh/disable configuration: authenticate: all + authenticator: as-root arguments: username: help: Username of the user @@ -438,6 +456,7 @@ user: api: GET /users/ssh/keys configuration: authenticate: all + authenticator: as-root arguments: username: help: Username of the user @@ -450,6 +469,7 @@ user: api: POST /users/ssh/key configuration: authenticate: all + authenticator: as-root arguments: username: help: Username of the user @@ -467,6 +487,7 @@ user: api: DELETE /users/ssh/key configuration: authenticate: all + authenticator: as-root arguments: username: help: Username of the user @@ -497,6 +518,7 @@ domain: api: POST /domains configuration: authenticate: all + authenticator: as-root arguments: domain: help: Domain name to add @@ -515,6 +537,7 @@ domain: api: DELETE /domains/ configuration: authenticate: all + authenticator: as-root arguments: domain: help: Domain to delete @@ -734,6 +757,7 @@ app: api: POST /apps configuration: authenticate: all + authenticator: as-root arguments: app: help: Name, local path or git URL of the app to install @@ -758,6 +782,7 @@ app: api: DELETE /apps/ configuration: authenticate: all + authenticator: as-root arguments: app: help: App(s) to delete @@ -768,7 +793,7 @@ app: api: PUT /upgrade/apps configuration: authenticate: all - authenticator: ldap-anonymous + authenticator: as-root arguments: app: help: App(s) to upgrade (default all) @@ -786,6 +811,7 @@ app: api: PUT /apps//changeurl configuration: authenticate: all + authenticator: as-root arguments: app: help: Target app instance name @@ -931,6 +957,7 @@ app: api: PUT /access configuration: authenticate: all + authenticator: as-root arguments: apps: nargs: "+" @@ -944,6 +971,7 @@ app: api: DELETE /access configuration: authenticate: all + authenticator: as-root arguments: apps: nargs: "+" @@ -957,6 +985,7 @@ app: api: POST /access configuration: authenticate: all + authenticator: as-root arguments: apps: nargs: "+" @@ -1659,6 +1688,7 @@ tools: api: POST /ldap configuration: authenticate: all + authenticator: as-root ### tools_adminpw() adminpw: @@ -1666,6 +1696,7 @@ tools: api: PUT /adminpw configuration: authenticate: all + authenticator: as-root arguments: -n: full: --new-password @@ -1683,6 +1714,7 @@ tools: - PUT /domains/main configuration: authenticate: all + authenticator: as-root arguments: -n: full: --new-domain @@ -1773,6 +1805,7 @@ tools: shell: configuration: authenticate: all + authenticator: as-root action_help: Launch a development shell arguments: -c: diff --git a/data/templates/slapd/slapd.conf b/data/templates/slapd/slapd.conf index 4acebe97e..5e76bdc01 100644 --- a/data/templates/slapd/slapd.conf +++ b/data/templates/slapd/slapd.conf @@ -82,6 +82,7 @@ checkpoint 512 30 # These access lines apply to database #1 only access to attrs=userPassword,shadowLastChange by dn="cn=admin,dc=yunohost,dc=org" write + by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write by anonymous auth by self write by * none @@ -91,6 +92,7 @@ access to attrs=userPassword,shadowLastChange # Others should be able to see it. access to attrs=cn,gecos,givenName,mail,maildrop,displayName,sn by dn="cn=admin,dc=yunohost,dc=org" write + by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write by self write by * read @@ -109,6 +111,7 @@ access to dn.base="" by * read # can read everything. access to * by dn="cn=admin,dc=yunohost,dc=org" write + by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write by * read diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index d58951878..38863686d 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -68,11 +68,11 @@ def tools_ldapinit(): """ # 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') + AUTH_IDENTIFIER = ('ldap', 'as-root') + AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} + auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: ldap_map = yaml.load(f) From 4c2ae4fc776b2f0d70e8d55e05507837d93ecdcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 28 Nov 2018 22:06:33 +0100 Subject: [PATCH 097/721] Implement permission helper --- data/helpers.d/setting | 70 ++++++++++++++++++++++++++++++++++++++ src/yunohost/app.py | 9 ++++- src/yunohost/permission.py | 5 +-- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index ad036ba4f..3267bf846 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -25,3 +25,73 @@ ynh_app_setting_set() { ynh_app_setting_delete() { sudo yunohost app setting -d "$1" "$2" --quiet } + +# Create a new permission for the app +# +# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--url "url" ["url" ...]] +# | arg: app - the application id +# | arg: permission - the name for the permission (by default a permission named "main" already exist) +# | arg: defaultdisallow - define if all user will be allowed by default +# | arg: url - the url for the the permission +ynh_permission_create() { + declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=url= ) + local app + local permission + local defaultdisallow + local url + ynh_handle_getopts_args "$@" + if [[ -n ${defaultdisallow:-} ]]; then + defaultdisallow=",default_allow=False" + fi + + if [[ -n ${url:-} ]]; then + url=",url=['${url//';'/"','"}']" + fi + yunohost tools shell -c "from yunohost.permission import permission_add; permission_add(auth, '$app', '$permission' ${defaultdisallow:-} ${url:-}, sync_perm=False)" +} + +# Remove a permission for the app (note that when the app is removed all permission is automatically removed) +# +# usage: ynh_permission_remove --app "app" --permission "permission" +# | arg: app - the application id +# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed) +ynh_permission_remove() { + declare -Ar args_array=( [a]=app= [p]=permission= ) + local app + local permission + ynh_handle_getopts_args "$@" + + yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove(auth, '$app', '$permission')" +} + +# Add a path managed by the SSO +# +# usage: ynh_permission_add_path --app "app" --permission "permission" --url "url" ["url" ...] +# | arg: app - the application id +# | arg: permission - the name for the permission +# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) +ynh_permission_add_path() { + declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) + local app + local permission + local url + ynh_handle_getopts_args "$@" + + yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', add_url=['${url//';'/"','"}'])" +} + +# Remove a path managed by the SSO +# +# usage: ynh_permission_del_path --app "app" --permission "permission" --url "url" ["url" ...] +# | arg: app - the application id +# | arg: permission - the name for the permission +# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin) +ynh_permission_del_path() { + declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= ) + local app + local permission + local url + ynh_handle_getopts_args "$@" + + yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', remove_url=['${url//';'/"','"}'])" +} diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 72755a5c5..99b688322 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -710,7 +710,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on """ from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger - from yunohost.permission import permission_add, permission_update + from yunohost.permission import permission_add, permission_update, permission_remove # Fetch or extract sources try: @@ -867,6 +867,13 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on os.path.join(extracted_app_folder, 'scripts/remove'), args=[app_instance_name], env=env_dict_remove ) + # Remove all permission in LDAP + result = auth.search(base='ou=permission,dc=yunohost,dc=org', + filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn']) + permission_list = [p['cn'][0] for p in result] + for l in permission_list: + permission_remove(auth, app_instance_name, l.split('.')[0], force=True) + if remove_retcode != 0: msg = m18n.n('app_not_properly_removed', app=app_instance_name) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index df53e9b6b..4b77df70f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -316,7 +316,7 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None): @is_unit_operation(['permission','app']) -def permission_add(operation_logger, auth, app, permission, url=None): +def permission_add(operation_logger, auth, app, permission, url=None, default_allow=True): """ Create a new permission for a specific application @@ -348,8 +348,9 @@ def permission_add(operation_logger, auth, app, permission, url=None): 'objectClass': ['top', 'permissionYnh', 'posixGroup'], 'cn': permission_name, 'gidNumber': gid, - 'groupPermission': 'cn=all_users,ou=groups,dc=yunohost,dc=org' } + if default_allow: + attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org' if url: attr_dict['URL'] = [] From b01e4b61f5c285ea54dd38c2d3c9c1b3a59fce7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 29 Nov 2018 22:49:45 +0100 Subject: [PATCH 098/721] Implement backup - restore and update test --- data/hooks/backup/05-conf_ldap | 6 ++++-- locales/en.json | 1 + src/yunohost/backup.py | 9 +++++++++ src/yunohost/tests/test_appurl.py | 8 +++++--- src/yunohost/tests/test_backuprestore.py | 6 ++++-- src/yunohost/tests/test_changeurl.py | 6 ++++-- 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/data/hooks/backup/05-conf_ldap b/data/hooks/backup/05-conf_ldap index b21103ede..8e593577a 100755 --- a/data/hooks/backup/05-conf_ldap +++ b/data/hooks/backup/05-conf_ldap @@ -13,5 +13,7 @@ backup_dir="${1}/conf/ldap" ynh_backup "/etc/ldap/slapd.conf" "${backup_dir}/slapd.conf" sudo slapcat -b cn=config -l "${backup_dir}/cn=config.master.ldif" -# Backup the database -sudo slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif" +# Backup the database (all but not the permission except the permission for mail, metronome and sftp +sudo slapcat -b dc=yunohost,dc=org \ + -H 'ldap:///dc=yunohost,dc=org???(|(!(objectClass=permissionYnh))(cn=main.mail)(cn=main.metronome)(cn=main.sftp))' \ + -l "${backup_dir}/dc=yunohost-dc=org.ldif" diff --git a/locales/en.json b/locales/en.json index 51cb01b0b..f1fe0252a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -105,6 +105,7 @@ "backup_output_directory_not_empty": "The output directory is not empty", "backup_output_directory_required": "You must provide an output directory for the backup", "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.", + "backup_permission": "Backup permission for app {app:s}", "backup_php5_to_php7_migration_may_fail": "Could not convert your archive to support php7, your php apps may fail to restore (reason: {error:s})", "backup_running_app_script": "Running backup script of app '{app:s}'…", "backup_running_hooks": "Running backup hooks…", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 0c8445bd6..00e9a1e21 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -685,6 +685,12 @@ class BackupManager(): raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict) self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"]) + + # backup permissions + logger.debug(m18n.n('backup_permission', app=app)) + ldap_url = "ldap:///dc=yunohost,dc=org???(&(objectClass=permissionYnh)(cn=*.%s))" % app + os.system("slapcat -b dc=yunohost,dc=org -H '%s' -l '%s/permission.ldif'" % (ldap_url, settings_dir)) + except: abs_tmp_app_dir = os.path.join(self.work_dir, 'apps/', app) shutil.rmtree(abs_tmp_app_dir, ignore_errors=True) @@ -1279,6 +1285,9 @@ class RestoreManager(): filesystem.chmod(app_settings_new_path, 0o400, 0o400, True) filesystem.chown(app_scripts_new_path, 'admin', None, True) + # Restore permissions + os.system("slapadd -l '%s/permission.ldif'" % app_settings_in_archive) + # Copy the app scripts to a writable temporary folder # FIXME : use 'install -Dm555' or something similar to what's done # in the backup method ? diff --git a/src/yunohost/tests/test_appurl.py b/src/yunohost/tests/test_appurl.py index 3a3a1db35..3234fefcb 100644 --- a/src/yunohost/tests/test_appurl.py +++ b/src/yunohost/tests/test_appurl.py @@ -6,9 +6,11 @@ from yunohost.app import app_install, app_remove from yunohost.domain import _get_maindomain, domain_url_available, _normalize_domain_path # Instantiate LDAP Authenticator -auth_identifier = ('ldap', 'ldap-anonymous') -auth_parameters = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'} -auth = init_authenticator(auth_identifier, auth_parameters) +AUTH_IDENTIFIER = ('ldap', 'as-root') +AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} +auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) # Get main domain diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 14c479d9a..5775e1612 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -18,8 +18,10 @@ from yunohost.utils.error import YunohostError maindomain = "" # Instantiate LDAP Authenticator -AUTH_IDENTIFIER = ('ldap', 'ldap-anonymous') -AUTH_PARAMETERS = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'} +AUTH_IDENTIFIER = ('ldap', 'as-root') +AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} auth = None diff --git a/src/yunohost/tests/test_changeurl.py b/src/yunohost/tests/test_changeurl.py index d37d3ed48..4594dd6b9 100644 --- a/src/yunohost/tests/test_changeurl.py +++ b/src/yunohost/tests/test_changeurl.py @@ -9,8 +9,10 @@ from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError # Instantiate LDAP Authenticator -AUTH_IDENTIFIER = ('ldap', 'ldap-anonymous') -AUTH_PARAMETERS = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'} +AUTH_IDENTIFIER = ('ldap', 'as-root') +AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) From c52ae326313e8449b478740d9b167a2177e091e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 1 Dec 2018 23:21:41 +0100 Subject: [PATCH 099/721] Fix traduction --- locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.json b/locales/en.json index f1fe0252a..6b088537a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -182,6 +182,7 @@ "dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.", "dyndns_unavailable": "Domain {domain:s} is not available.", "edit_group_not_allowed": "You are not allowed to edit the group {group:s}", + "edit_permission_with_group_all_users_not_allowed": "You are not allowed to edit permission for group 'all_users', use 'yunohost user permission clear APP' or 'yunohost user permission add APP -u USER' instead.", "executing_command": "Executing command '{command:s}'…", "executing_script": "Executing script '{script:s}'…", "extracting": "Extracting…", From 9ac60be564aff15b9e07ec348dc2ea7c7525b034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 7 Dec 2018 15:26:04 +0100 Subject: [PATCH 100/721] Sync permission only at end of each operation --- data/helpers.d/setting | 6 ++-- locales/en.json | 1 + src/yunohost/app.py | 22 +++++++------- src/yunohost/permission.py | 30 +++++++++++-------- src/yunohost/user.py | 61 ++++++++++++++++++-------------------- 5 files changed, 62 insertions(+), 58 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 3267bf846..585c7ccd0 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -61,7 +61,7 @@ ynh_permission_remove() { local permission ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove(auth, '$app', '$permission')" + yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove(auth, '$app', '$permission', sync_perm=False)" } # Add a path managed by the SSO @@ -77,7 +77,7 @@ ynh_permission_add_path() { local url ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', add_url=['${url//';'/"','"}'])" + yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)" } # Remove a path managed by the SSO @@ -93,5 +93,5 @@ ynh_permission_del_path() { local url ynh_handle_getopts_args "$@" - yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', remove_url=['${url//';'/"','"}'])" + yunohost tools shell -c "from yunohost.permission import permission_update; permission_update(auth, '$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)" } diff --git a/locales/en.json b/locales/en.json index 6b088537a..3a629ea0e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -403,6 +403,7 @@ "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", "permission_name_not_valid": "Permission name '{permission:s}' not valid", "permission_update_failed": "Permission update failed", + "permission_generated": "Permission updated", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", "permission_update_nothing_to_do": "Permission update nothing to do", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 99b688322..023007789 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -536,9 +536,7 @@ def app_change_url(operation_logger, auth, app, domain, path): app_setting(app, 'domain', value=domain) app_setting(app, 'path', value=path) - permission_update(auth, app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path]) - - app_ssowatconf(auth) + permission_update(auth, app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path], sync_perm=True) # avoid common mistakes if _run_service_command("reload", "nginx") == False: @@ -568,6 +566,7 @@ def app_upgrade(auth, app=[], url=None, file=None): """ from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback + from yunohost.permission import permission_sync_to_user # Retrieve interface is_api = msettings.get('interface') == 'api' @@ -687,7 +686,7 @@ def app_upgrade(auth, app=[], url=None, file=None): if not upgraded_apps: raise YunohostError('app_no_upgrade') - app_ssowatconf(auth) + permission_sync_to_user(auth) logger.success(m18n.n('upgrade_complete')) @@ -710,7 +709,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on """ from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger - from yunohost.permission import permission_add, permission_update, permission_remove + from yunohost.permission import permission_add, permission_update, permission_remove, permission_sync_to_user # Fetch or extract sources try: @@ -915,9 +914,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on domain = app_settings['domain'] path = app_settings.get('path', '/') if domain and path: - permission_update(auth, app_instance_name, permission="main", add_url=[domain+path]) + permission_update(auth, app_instance_name, permission="main", add_url=[domain+path], sync_perm=False) - app_ssowatconf(auth) + permission_sync_to_user(auth) logger.success(m18n.n('installation_complete')) @@ -934,7 +933,7 @@ def app_remove(operation_logger, auth, app): """ from yunohost.hook import hook_exec, hook_remove, hook_callback - from yunohost.permission import permission_remove + from yunohost.permission import permission_remove, permission_sync_to_user if not _is_installed(app): raise YunohostError('app_not_installed', app=app) @@ -982,9 +981,9 @@ def app_remove(operation_logger, auth, app): filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app, attrs=['cn']) permission_list = [p['cn'][0] for p in result] for l in permission_list: - permission_remove(auth, app, l.split('.')[0], force=True) + permission_remove(auth, app, l.split('.')[0], force=True, sync_perm=False) - app_ssowatconf(auth) + permission_sync_to_user(auth) @is_unit_operation(['permission','app']) def app_addaccess(operation_logger, auth, apps, users=[]): @@ -1037,6 +1036,9 @@ def app_clearaccess(operation_logger, auth, apps): user_permission_clear(operation_logger, auth, app=apps, permission="main") + result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} + + return {'allowed_users': result} def app_debug(app): """ diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 4b77df70f..6b77118c5 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -32,7 +32,6 @@ from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger from yunohost.user import user_list, user_group_list -from yunohost.app import app_ssowatconf from yunohost.log import is_unit_operation logger = getActionLogger('yunohost.user') @@ -232,7 +231,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ else: raise MoulinetteError(169, m18n.n('permission_update_failed')) - _permission_sync_to_user(auth) + permission_sync_to_user(auth) for a in app: allowed_users = set() @@ -253,11 +252,10 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ if del_group: hook_callback('post_app_removeaccess', args=[app, disallowed_users]) - app_ssowatconf(auth) return user_permission_list(auth, app, permission) -def user_permission_clear(operation_logger, auth, app=[], permission=None): +def user_permission_clear(operation_logger, auth, app=[], permission=None, sync_perm=True): """ Reset the permission for a specific application @@ -300,7 +298,7 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None): else: raise MoulinetteError(169, m18n.n('permission_update_failed')) - _permission_sync_to_user(auth) + permission_sync_to_user(auth) for a in app: permission_name = 'main.' + a @@ -311,12 +309,11 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None): new_user_list = ','.join(allowed_users) hook_callback('post_app_removeaccess', args=[app, new_user_list]) - app_ssowatconf(auth) return user_permission_list(auth, app, permission) @is_unit_operation(['permission','app']) -def permission_add(operation_logger, auth, app, permission, url=None, default_allow=True): +def permission_add(operation_logger, auth, app, permission, url=None, default_allow=True, sync_perm=True): """ Create a new permission for a specific application @@ -362,7 +359,8 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al operation_logger.start() if auth.add('cn=%s,ou=permission' % permission_name, attr_dict): - _permission_sync_to_user(auth) + if sync_perm: + permission_sync_to_user(auth) logger.success(m18n.n('permission_created', permission=permission, app=app)) return user_permission_list(auth, app, permission) @@ -370,7 +368,7 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al @is_unit_operation(['permission','app']) -def permission_update(operation_logger, auth, app, permission, add_url=None, remove_url=None): +def permission_update(operation_logger, auth, app, permission, add_url=None, remove_url=None, sync_perm=True): """ Update a permission for a specific application @@ -416,7 +414,8 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem operation_logger.start() if auth.update('cn=%s,ou=permission' % permission_name, {'cn':permission_name, 'URL': url}): - _permission_sync_to_user(auth) + if sync_perm: + permission_sync_to_user(auth) logger.success(m18n.n('permission_updated', permission=permission, app=app)) return user_permission_list(auth, app, permission) @@ -424,7 +423,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem @is_unit_operation(['permission','app']) -def permission_remove(operation_logger, auth, app, permission, force=False): +def permission_remove(operation_logger, auth, app, permission, force=False, sync_perm=True): """ Remove a permission for a specific application @@ -440,15 +439,17 @@ def permission_remove(operation_logger, auth, app, permission, force=False): operation_logger.start() if not auth.remove('cn=%s,ou=permission' % str(permission + '.' + app)): raise MoulinetteError(169, m18n.n('permission_deletion_failed', permission=permission, app=app)) - _permission_sync_to_user(auth) + if sync_perm: + permission_sync_to_user(auth) logger.success(m18n.n('permission_deleted', permission=permission, app=app)) -def _permission_sync_to_user(auth): +def permission_sync_to_user(auth): """ Sychronise the inheritPermission attribut in the permission object from the user<->group link and the group<->permission link """ import os + from yunohost.app import app_ssowatconf permission_attrs = [ 'cn', @@ -486,6 +487,9 @@ def _permission_sync_to_user(auth): inheritPermission = {'inheritPermission': val, 'memberUid': uid_val} if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): raise MoulinetteError(169, m18n.n('permission_update_failed')) + logger.success(m18n.n('permission_generated')) + + app_ssowatconf(auth) # Reload unscd because if not the group is not updated in the system from LDAP os.system('systemctl restart unscd') diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 32cb6f684..22b2e1b75 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -115,7 +115,6 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas """ from yunohost.domain import domain_list, _get_maindomain from yunohost.hook import hook_callback - from yunohost.app import app_ssowatconf from yunohost.utils.password import assert_password_is_strong_enough # Ensure sufficiently complex password @@ -211,13 +210,14 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas if not os.path.isdir('/home/{0}'.format(username)): logger.warning(m18n.n('user_home_creation_failed'), exc_info=1) - app_ssowatconf(auth) + + # Create group for user and add to group 'all_users' + user_group_add(auth, groupname=username, gid=uid, sync_perm=False) + user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) + user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=True) + # TODO: Send a welcome mail to user logger.success(m18n.n('user_created')) - # Create group for user and add to group 'all_users' - user_group_add(auth, groupname=username, gid=uid) - user_group_update(auth, groupname=username, add_user=username, force=True) - user_group_update(auth, 'all_users', add_user=username, force=True) hook_callback('post_user_create', args=[username, mail, password, firstname, lastname]) @@ -237,7 +237,6 @@ def user_delete(operation_logger, auth, username, purge=False): purge """ - from yunohost.app import app_ssowatconf from yunohost.hook import hook_callback operation_logger.start() @@ -250,7 +249,7 @@ def user_delete(operation_logger, auth, username, purge=False): else: raise YunohostError('user_deletion_failed') - user_group_delete(auth, username, force=True) + user_group_delete(auth, username, force=True, sync_perm=True) group_list = auth.search('ou=groups,dc=yunohost,dc=org', '(&(objectclass=groupOfNamesYnh)(memberUid=%s))' @@ -263,8 +262,6 @@ def user_delete(operation_logger, auth, username, purge=False): if not auth.update('cn=%s,ou=groups' % group['cn'][0], user_list): raise YunohostError('group_update_failed') - app_ssowatconf(auth) - hook_callback('post_user_delete', args=[username, purge]) logger.success(m18n.n('user_deleted')) @@ -537,7 +534,7 @@ def user_group_list(auth, fields=None): @is_unit_operation([('groupname', 'user')]) -def user_group_add(operation_logger, auth, groupname,gid=None): +def user_group_add(operation_logger, auth, groupname,gid=None, sync_perm=True): """ Create group @@ -545,8 +542,7 @@ def user_group_add(operation_logger, auth, groupname,gid=None): groupname -- Must be unique """ - from yunohost.app import app_ssowatconf - from yunohost.permission import _permission_sync_to_user + from yunohost.permission import permission_sync_to_user operation_logger.start() @@ -578,16 +574,16 @@ def user_group_add(operation_logger, auth, groupname,gid=None): } if auth.add('cn=%s,ou=groups' % groupname, attr_dict): - _permission_sync_to_user(auth) - app_ssowatconf(auth) logger.success(m18n.n('group_created')) + if sync_perm: + permission_sync_to_user(auth) return {'name': groupname} raise MoulinetteError(169, m18n.n('group_creation_failed')) @is_unit_operation([('groupname', 'user')]) -def user_group_delete(operation_logger, auth, groupname, force=False): +def user_group_delete(operation_logger, auth, groupname, force=False, sync_perm=True): """ Delete user @@ -595,8 +591,7 @@ def user_group_delete(operation_logger, auth, groupname, force=False): groupname -- Groupname to delete """ - from yunohost.app import app_ssowatconf - from yunohost.permission import _permission_sync_to_user + from yunohost.permission import permission_sync_to_user if not force and (groupname == 'all_users' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): raise MoulinetteError(errno.EPERM, m18n.n('group_deletion_not_allowed', user=groupname)) @@ -605,13 +600,13 @@ def user_group_delete(operation_logger, auth, groupname, force=False): if not auth.remove('cn=%s,ou=groups' % groupname): raise MoulinetteError(169, m18n.n('group_deletion_failed')) - _permission_sync_to_user(auth) - app_ssowatconf(auth) logger.success(m18n.n('group_deleted')) + if sync_perm: + permission_sync_to_user(auth) @is_unit_operation([('groupname', 'user')]) -def user_group_update(operation_logger, auth, groupname, add_user=None, remove_user=None, force=False): +def user_group_update(operation_logger, auth, groupname, add_user=None, remove_user=None, force=False, sync_perm=True): """ Update user informations @@ -622,8 +617,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u """ - from yunohost.app import app_ssowatconf - from yunohost.permission import _permission_sync_to_user + from yunohost.permission import permission_sync_to_user attrs_to_fetch = ['member'] @@ -685,9 +679,9 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u if not auth.update('cn=%s,ou=groups' % groupname, new_group_list): raise MoulinetteError(169, m18n.n('group_update_failed')) - _permission_sync_to_user(auth) logger.success(m18n.n('group_updated')) - app_ssowatconf(auth) + if sync_perm: + permission_sync_to_user(auth) return user_group_info(auth, groupname) @@ -723,24 +717,27 @@ def user_group_info(auth, groupname): # import yunohost.permission -def user_permission_list(auth, app=None, permission=None, username=None, group=None): +def user_permission_list(auth, app=None, permission=None, username=None, group=None, sync_perm=True): return yunohost.permission.user_permission_list(auth, app, permission, username, group) @is_unit_operation([('app', 'user')]) -def user_permission_add(operation_logger, auth, app, permission="main", username=None, group=None): +def user_permission_add(operation_logger, auth, app, permission="main", username=None, group=None, sync_perm=True): return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, add_username=username, add_group=group, - del_username=None, del_group=None) + del_username=None, del_group=None, + sync_perm=sync_perm) @is_unit_operation([('app', 'user')]) -def user_permission_remove(operation_logger, auth, app, permission="main", username=None, group=None): +def user_permission_remove(operation_logger, auth, app, permission="main", username=None, group=None, sync_perm=True): return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, add_username=None, add_group=None, - del_username=username, del_group=group) + del_username=username, del_group=group, + sync_perm=sync_perm) @is_unit_operation([('app', 'user')]) -def user_permission_clear(operation_logger, auth, app, permission=None): - return yunohost.permission.user_permission_clear(operation_logger, auth, app, permission) +def user_permission_clear(operation_logger, auth, app, permission=None, sync_perm=True): + return yunohost.permission.user_permission_clear(operation_logger, auth, app, permission, + sync_perm=sync_perm) # # SSH subcategory From 811d1f6a9c03fad10a6fda48ae70ae8cd879fd47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 8 Dec 2018 00:41:42 +0100 Subject: [PATCH 101/721] Fix app_setings_get for path --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 023007789..f9b7191c0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -912,7 +912,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on # Add path in permission if it's defined in the app install script app_settings = _get_app_settings(app_instance_name) domain = app_settings['domain'] - path = app_settings.get('path', '/') + path = app_settings.get('path', None) if domain and path: permission_update(auth, app_instance_name, permission="main", add_url=[domain+path], sync_perm=False) From 0d3d33fdce4978d9b96baf282b17d697dc02a618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 14 Dec 2018 23:49:26 +0100 Subject: [PATCH 102/721] Implement migration for group and permission --- locales/en.json | 7 ++ .../0009_setup_group_permission.py | 108 ++++++++++++++++++ src/yunohost/permission.py | 5 +- 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 src/yunohost/data_migrations/0009_setup_group_permission.py diff --git a/locales/en.json b/locales/en.json index 3a629ea0e..23b9c5751 100644 --- a/locales/en.json +++ b/locales/en.json @@ -307,6 +307,7 @@ "migration_description_0006_sync_admin_and_root_passwords": "Synchronize admin and root passwords", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", + "migration_description_0009_setup_group_permission": "Setup user group and setup permission for apps and services", "migration_0003_backward_impossible": "The stretch migration cannot be reverted.", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", @@ -332,6 +333,12 @@ "migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", + "migration_0009_create_group": "Create group for each user.", + "migration_0009_disclaimer": "Your LDAP schema will be updated and your LDAP database will be updated", + "migration_0009_done": "Migration sucess. You are now able to use groups of user.", + "migration_0009_migrate_permission": "Migrate permission from apps settings to LDAP", + "migration_0009_update_LDAP_database": "Update LDAP database for groups and permission support", + "migration_0009_update_LDAP_schema": "Update LDAP schema", "migrations_backward": "Migrating backward.", "migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py new file mode 100644 index 000000000..2b88388eb --- /dev/null +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -0,0 +1,108 @@ +import yaml +import errno + +from moulinette import m18n +from moulinette.core import MoulinetteError, init_authenticator +from moulinette.utils.log import getActionLogger + +from yunohost.tools import Migration +from yunohost.utils.filesystem import free_space_in_directory, space_used_by_directory +from yunohost.user import user_list, user_group_add, user_group_update +from yunohost.app import app_setting, app_list +from yunohost.service import service_regen_conf +from yunohost.permission import permission_add, permission_sync_to_user +from yunohost.user import user_permission_add + +logger = getActionLogger('yunohost.migration') + +################################################### +# Tools used also for restoration +################################################### + +def migrate_LDAP_db(auth): + logger.info(m18n.n("migration_0009_update_LDAP_database")) + try: + auth.remove('cn=sftpusers,ou=groups') + except Exception as e: + logger.warn("Error when trying remove sftpusers group") + + with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: + ldap_map = yaml.load(f) + + try: + attr_dict = ldap_map['parents']['ou=permission'] + auth.add('ou=permission', attr_dict) + + attr_dict = ldap_map['children']['cn=all_users,ou=groups'] + auth.add('cn=all_users,ou=groups', attr_dict) + + for rdn, attr_dict in ldap_map['depends_children'].items(): + auth.add(rdn, attr_dict) + except Exception as e: + raise MoulinetteError(errno.EINVAL, m18n.n(("LDAP_update_failled"))) + + logger.info(m18n.n("migration_0009_create_group")) + + #Create group for each yunohost user + user_list = auth.search('ou=users,dc=yunohost,dc=org', + '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', + ['uid', 'uidNumber']) + for user_info in user_list: + username = user_info['uid'][0] + user_group_add(auth, username, gid=user_info['uidNumber'][0], sync_perm=False) + user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) + user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=False) + + +def migrate_app_permission(auth): + logger.info(m18n.n("migration_0009_migrate_permission")) + + for app_info in app_list(installed=True)['apps']: + app = app_info['id'] + permission = app_setting(app, 'allowed_users') + path = app_setting(app, 'path') + domain = app_setting(app, 'domain') + + url = None + if domain and path: + url = domain + path + permission_add(auth, app, 'main', url=url, default_allow=True, sync_perm=False) + if permission: + allowed_group = permission.split(',') + user_permission_add(auth, [app], 'main', group=allowed_group, sync_perm=False) + app_setting(app, 'allowed_users', delete=True) + + +class MyMigration(Migration): + """ + Update the LDAP DB to be able to store the permission + Create a group for each yunohost user + Migrate app permission from apps setting to LDAP + """ + + required = True + + def migrate(self): + # Update LDAP schema restart slapd + logger.info(m18n.n("migration_0009_update_LDAP_schema")) + service_regen_conf(names=['slapd'], force=True) + + # Do the authentication to LDAP after LDAP as been updated + AUTH_IDENTIFIER = ('ldap', 'as-root') + AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} + auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) + + #Update LDAP database + migrate_LDAP_db(auth) + + # Migrate permission + migrate_app_permission(auth) + + permission_sync_to_user(auth) + logger.info(m18n.n("migration_0009_done")) + + @property + def disclaimer(self): + return m18n.n("migration_0009_disclaimer") diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 6b77118c5..0cf77745a 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -114,7 +114,7 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N return {'permissions': permissions} -def user_permission_update(operation_logger, auth, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None): +def user_permission_update(operation_logger, auth, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None, sync_perm=True): """ Allow or Disallow a user or group to a permission for a specific application @@ -231,7 +231,8 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ else: raise MoulinetteError(169, m18n.n('permission_update_failed')) - permission_sync_to_user(auth) + if sync_perm: + permission_sync_to_user(auth) for a in app: allowed_users = set() From f22e7144b45cf2d6f32096de980e6bb0c37936e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 12 Dec 2018 17:40:29 +0100 Subject: [PATCH 103/721] Implement migration while restore backup --- data/actionsmap/yunohost.yml | 2 +- src/yunohost/backup.py | 45 ++++++++++++++----- .../0009_setup_group_permission.py | 9 +++- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 9676baf52..8324bdb8e 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1085,7 +1085,7 @@ backup: api: POST /backup/restore/ configuration: authenticate: all - authenticator: ldap-anonymous + authenticator: as-root arguments: name: help: Name of the local backup archive diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 00e9a1e21..a2001458b 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -800,14 +800,14 @@ class RestoreManager(): Public methods: set_targets(self, system_parts=[], apps=[]) - restore(self) + restore(self, auth) Usage: restore_manager = RestoreManager(name) restore_manager.set_targets(None, ['wordpress__3']) - restore_manager.restore() + restore_manager.restore(auth) if restore_manager.success: logger.success(m18n.n('restore_complete')) @@ -893,6 +893,23 @@ class RestoreManager(): logger.debug("executing the post-install...") tools_postinstall(domain, 'yunohost', True) + def _migrate_system_if_needed(self, auth): + """ + Do some migration if needed + """ + + # Check if we need to do the migration 0009 : setup group and permission + result = auth.search('ou=groups,dc=yunohost,dc=org', + '(&(objectclass=groupOfNamesYnh)(cn=all_users))', + ['cn']) + if not result: + from importlib import import_module + migration_0009 = import_module('yunohost.data_migrations.0009_setup_group_permission') + # Update LDAP schema restart slapd + logger.info(m18n.n("migration_0009_update_LDAP_schema")) + service_regen_conf(names=['slapd'], force=True) + migration_0009.migrate_LDAP_db(auth) + def clean(self): """ End a restore operations by cleaning the working directory and @@ -1100,7 +1117,7 @@ class RestoreManager(): # "Actual restore" (reverse step of the backup collect part) # # - def restore(self): + def restore(self, auth): """ Restore the archive @@ -1114,8 +1131,9 @@ class RestoreManager(): # Apply dirty patch to redirect php5 file on php7 self._patch_backup_csv_file() - self._restore_system() - self._restore_apps() + self._restore_system(auth) + self._migrate_system_if_needed(auth) + self._restore_apps(auth) finally: self.clean() @@ -1159,7 +1177,7 @@ class RestoreManager(): logger.warning(m18n.n('backup_php5_to_php7_migration_may_fail', error=str(e))) - def _restore_system(self): + def _restore_system(self, auth): """ Restore user and system parts """ system_targets = self.targets.list("system", exclude=["Skipped"]) @@ -1199,15 +1217,15 @@ class RestoreManager(): service_regen_conf() - def _restore_apps(self): + def _restore_apps(self, auth): """Restore all apps targeted""" apps_targets = self.targets.list("apps", exclude=["Skipped"]) for app in apps_targets: - self._restore_app(app) + self._restore_app(auth, app) - def _restore_app(self, app_instance_name): + def _restore_app(self, auth, app_instance_name): """ Restore an app @@ -1286,7 +1304,12 @@ class RestoreManager(): filesystem.chown(app_scripts_new_path, 'admin', None, True) # Restore permissions - os.system("slapadd -l '%s/permission.ldif'" % app_settings_in_archive) + if os.path.isfile(app_restore_script_in_archive): + os.system("slapadd -l '%s/permission.ldif'" % app_settings_in_archive) + else: + from importlib import import_module + migration_0009 = import_module('yunohost.data_migrations.0009_setup_group_permission') + migration_0009.migrate_app_permission(auth, app=app_instance_name) # Copy the app scripts to a writable temporary folder # FIXME : use 'install -Dm555' or something similar to what's done @@ -2137,7 +2160,7 @@ def backup_restore(auth, name, system=[], apps=[], force=False): # restore_manager.mount() - restore_manager.restore() + restore_manager.restore(auth) # Check if something has been restored if restore_manager.success: diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 2b88388eb..e08f79724 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -54,10 +54,15 @@ def migrate_LDAP_db(auth): user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=False) -def migrate_app_permission(auth): +def migrate_app_permission(auth, app=None): logger.info(m18n.n("migration_0009_migrate_permission")) - for app_info in app_list(installed=True)['apps']: + if app: + apps = app_list(installed=True, filter=app)['apps'] + else: + apps = app_list(installed=True)['apps'] + + for app_info in apps: app = app_info['id'] permission = app_setting(app, 'allowed_users') path = app_setting(app, 'path') From ad3d8786d6d1fa94b5ce16e9434678890501a50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 15 Dec 2018 20:27:22 +0100 Subject: [PATCH 104/721] Change MoulinetteError to YunohostError --- src/yunohost/app.py | 2 +- .../0009_setup_group_permission.py | 6 +-- src/yunohost/permission.py | 37 +++++++++---------- src/yunohost/user.py | 26 ++++++------- 4 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f9b7191c0..94f6680ce 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1088,7 +1088,7 @@ def app_makedefault(operation_logger, auth, app, domain=None): operation_logger.start() if '/' in app_map(auth, raw=True)[domain]: raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, - other_app=app_map(auth, raw=True)[domain]["/"]["id"])) + other_app=app_map(auth, raw=True)[domain]["/"]["id"]) try: with open('/etc/ssowat/conf.json.persistent') as json_conf: diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index e08f79724..173a96709 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -1,8 +1,8 @@ import yaml -import errno from moulinette import m18n -from moulinette.core import MoulinetteError, init_authenticator +from moulinette.core import init_authenticator +from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration @@ -39,7 +39,7 @@ def migrate_LDAP_db(auth): for rdn, attr_dict in ldap_map['depends_children'].items(): auth.add(rdn, attr_dict) except Exception as e: - raise MoulinetteError(errno.EINVAL, m18n.n(("LDAP_update_failled"))) + raise YunohostError("LDAP_update_failled") logger.info(m18n.n("migration_0009_create_group")) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 0cf77745a..b7f2b9949 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -24,13 +24,12 @@ Manage permissions """ -import errno import grp import random from moulinette import m18n -from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger +from yunohost.utils.error import YunohostError from yunohost.user import user_list, user_group_list from yunohost.log import is_unit_operation @@ -163,23 +162,23 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ # Validate that the group exist for g in add_group: if not g in user_group_list(auth, ['cn'])['groups']: - raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=g)) + raise YunohostError('group_unknown', group=g) for u in add_username: if not u in user_list(auth, ['uid'])['users']: - raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=u)) + raise YunohostError('user_unknown', user=u) for g in del_group: if not g in user_group_list(auth, ['cn'])['groups']: - raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=g)) + raise YunohostError('group_unknown', group=g) for u in del_username: if not u in user_list(auth, ['uid'])['users']: - raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=u)) + raise YunohostError('user_unknown', user=u) # Merge user and group (note that we consider all user as a group) add_group.extend(add_username) del_group.extend(del_username) if 'all_users' in add_group or 'all_users' in del_group: - raise MoulinetteError(errno.EINVAL, m18n.n('edit_permission_with_group_all_users_not_allowed')) + raise YunohostError('edit_permission_with_group_all_users_not_allowed') # Populate permission informations permission_attrs = [ @@ -196,14 +195,14 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ for per in permission: permission_name = per + '.' + a if not permission_name in result: - raise MoulinetteError(errno.EINVAL, m18n.n('permission_not_found', permission=per, app=a)) + raise YunohostError('permission_not_found', permission=per, app=a) new_per_dict[permission_name] = set() if 'groupPermission' in result[permission_name]: new_per_dict[permission_name] = set(result[permission_name]['groupPermission']) for g in del_group: if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]: - raise MoulinetteError(errno.EINVAL, m18n.n('need_define_permission_before')) + raise YunohostError('need_define_permission_before') group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' if not group_name in new_per_dict[permission_name]: logger.warning(m18n.n('group_alread_disallowed', permission=per, app=a, group=g)) @@ -229,7 +228,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ p = per.split('.') logger.success(m18n.n('permission_updated', permission=p[0], app=p[1])) else: - raise MoulinetteError(169, m18n.n('permission_update_failed')) + raise YunohostError('permission_update_failed') if sync_perm: permission_sync_to_user(auth) @@ -290,14 +289,14 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None, sync_ for per in permission: permission_name = per + '.' + a if not permission_name in result: - raise MoulinetteError(errno.EINVAL, m18n.n('permission_not_found', permission=per, app=a)) + raise YunohostError('permission_not_found', permission=per, app=a) if 'groupPermission' in result[permission_name] and 'cn=all_users,ou=groups,dc=yunohost,dc=org' in result[permission_name]['groupPermission']: logger.warning(m18n.n('permission_already_clear', permission=per, app=a)) continue if auth.update('cn=%s,ou=permission' % permission_name, default_permission): logger.success(m18n.n('permission_updated', permission=per, app=a)) else: - raise MoulinetteError(169, m18n.n('permission_update_failed')) + raise YunohostError('permission_update_failed') permission_sync_to_user(auth) @@ -332,7 +331,7 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al 'cn': permission_name }, base_dn='ou=permission,dc=yunohost,dc=org') if conflict: - raise MoulinetteError(errno.EEXIST, m18n.n('permission_already_exist', permission=permission, app=app)) + raise YunohostError('permission_already_exist', permission=permission, app=app) # Get random GID all_gid = {x.gr_gid for x in grp.getgrall()} @@ -365,7 +364,7 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al logger.success(m18n.n('permission_created', permission=permission, app=app)) return user_permission_list(auth, app, permission) - raise MoulinetteError(169, m18n.n('premission_creation_failled')) + raise YunohostError('premission_creation_failled') @is_unit_operation(['permission','app']) @@ -388,7 +387,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem result = auth.search(base='ou=permission,dc=yunohost,dc=org', filter='cn=' + permission_name, attrs=['URL']) if not result: - raise MoulinetteError(errno.EINVAL, m18n.n('permission_not_found', permission=permission, app=app)) + raise YunohostError('permission_not_found', permission=permission, app=app) permission_obj = result[0] if not 'URL' in permission_obj: @@ -420,7 +419,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem logger.success(m18n.n('permission_updated', permission=permission, app=app)) return user_permission_list(auth, app, permission) - raise MoulinetteError(169, m18n.n('premission_update_failled')) + raise YunohostError('premission_update_failled') @is_unit_operation(['permission','app']) @@ -435,11 +434,11 @@ def permission_remove(operation_logger, auth, app, permission, force=False, sync """ if permission == "main" and not force: - raise MoulinetteError(errno.EPERM, m18n.n('remove_main_permission_not_allowed')) + raise YunohostError('remove_main_permission_not_allowed') operation_logger.start() if not auth.remove('cn=%s,ou=permission' % str(permission + '.' + app)): - raise MoulinetteError(169, m18n.n('permission_deletion_failed', permission=permission, app=app)) + raise YunohostError('permission_deletion_failed', permission=permission, app=app) if sync_perm: permission_sync_to_user(auth) logger.success(m18n.n('permission_deleted', permission=permission, app=app)) @@ -487,7 +486,7 @@ def permission_sync_to_user(auth): uid_val = [v.split("=")[1].split(",")[0] for v in val] inheritPermission = {'inheritPermission': val, 'memberUid': uid_val} if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): - raise MoulinetteError(169, m18n.n('permission_update_failed')) + raise YunohostError('permission_update_failed') logger.success(m18n.n('permission_generated')) app_ssowatconf(auth) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 22b2e1b75..b800c9b0e 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -496,8 +496,7 @@ def user_group_list(auth, fields=None): if attr in keys: attrs.append(attr) else: - raise MoulinetteError(errno.EINVAL, - m18n.n('field_invalid', attr)) + raise YunohostError('field_invalid', attr) else: attrs = ['cn', 'member'] @@ -551,12 +550,12 @@ def user_group_add(operation_logger, auth, groupname,gid=None, sync_perm=True): 'cn': groupname }, base_dn='ou=groups,dc=yunohost,dc=org') if conflict: - raise MoulinetteError(errno.EEXIST, m18n.n('group_name_already_exist', name=groupname)) + raise YunohostError('group_name_already_exist', name=groupname) # Validate uniqueness of groupname in system group all_existing_groupnames = {x.gr_name for x in grp.getgrall()} if groupname in all_existing_groupnames: - raise MoulinetteError(errno.EEXIST, m18n.n('system_groupname_exists')) + raise YunohostError('system_groupname_exists') if not gid: # Get random GID @@ -579,7 +578,7 @@ def user_group_add(operation_logger, auth, groupname,gid=None, sync_perm=True): permission_sync_to_user(auth) return {'name': groupname} - raise MoulinetteError(169, m18n.n('group_creation_failed')) + raise YunohostError('group_creation_failed') @is_unit_operation([('groupname', 'user')]) @@ -594,11 +593,11 @@ def user_group_delete(operation_logger, auth, groupname, force=False, sync_perm= from yunohost.permission import permission_sync_to_user if not force and (groupname == 'all_users' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): - raise MoulinetteError(errno.EPERM, m18n.n('group_deletion_not_allowed', user=groupname)) + raise YunohostError('group_deletion_not_allowed', user=groupname) operation_logger.start() if not auth.remove('cn=%s,ou=groups' % groupname): - raise MoulinetteError(169, m18n.n('group_deletion_failed')) + raise YunohostError('group_deletion_failed') logger.success(m18n.n('group_deleted')) if sync_perm: @@ -622,13 +621,13 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u attrs_to_fetch = ['member'] if (groupname == 'all_users' or groupname == 'admins') and not force: - raise MoulinetteError(errno.EINVAL, m18n.n('edit_group_not_allowed', group=groupname)) + raise YunohostError('edit_group_not_allowed', group=groupname) # Populate group informations result = auth.search(base='ou=groups,dc=yunohost,dc=org', filter='cn=' + groupname, attrs=attrs_to_fetch) if not result: - raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=groupname)) + raise YunohostError('group_unknown', group=groupname) group = result[0] new_group_list = {'member': set(), 'memberUid': set()} @@ -644,7 +643,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u add_user = [add_user] for user in add_user: if not user in user_l: - raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=user)) + raise YunohostError('user_unknown', user=user) userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if userDN in group['member']: logger.warning(m18n.n('user_alread_in_group', user=user, group=groupname)) @@ -656,8 +655,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u for user in remove_user: userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if user == groupname: - raise MoulinetteError(errno.EINVAL, - m18n.n('remove_user_of_group_not_allowed', user=user, group=groupname)) + raise YunohostError('remove_user_of_group_not_allowed', user=user, group=groupname) if 'member' in group and userDN in group['member']: new_group_list['member'].remove(userDN) else: @@ -677,7 +675,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u if new_group_list['member'] != set(group['member']): if not auth.update('cn=%s,ou=groups' % groupname, new_group_list): - raise MoulinetteError(169, m18n.n('group_update_failed')) + raise YunohostError('group_update_failed') logger.success(m18n.n('group_updated')) if sync_perm: @@ -699,7 +697,7 @@ def user_group_info(auth, groupname): result = auth.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname, group_attrs) if not result: - raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=groupname)) + raise YunohostError('group_unknown', group=groupname) else: group = result[0] From 1de4625d01302eed189da2ea4fd19e94bed65b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 28 Dec 2018 10:12:08 +0100 Subject: [PATCH 105/721] Remove disclaimer for migration --- locales/en.json | 1 - src/yunohost/data_migrations/0009_setup_group_permission.py | 4 ---- 2 files changed, 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index 23b9c5751..3010edf17 100644 --- a/locales/en.json +++ b/locales/en.json @@ -334,7 +334,6 @@ "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0009_create_group": "Create group for each user.", - "migration_0009_disclaimer": "Your LDAP schema will be updated and your LDAP database will be updated", "migration_0009_done": "Migration sucess. You are now able to use groups of user.", "migration_0009_migrate_permission": "Migrate permission from apps settings to LDAP", "migration_0009_update_LDAP_database": "Update LDAP database for groups and permission support", diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 173a96709..5767e20d5 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -107,7 +107,3 @@ class MyMigration(Migration): permission_sync_to_user(auth) logger.info(m18n.n("migration_0009_done")) - - @property - def disclaimer(self): - return m18n.n("migration_0009_disclaimer") From 6d3cb916b5cfdf47f3d8e1a7c5fe519c79569bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 28 Dec 2018 10:23:22 +0100 Subject: [PATCH 106/721] Improve migration --- locales/en.json | 7 +++ .../0009_setup_group_permission.py | 51 +++++++++++++++---- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/locales/en.json b/locales/en.json index 3010edf17..45c1fbb90 100644 --- a/locales/en.json +++ b/locales/en.json @@ -183,6 +183,7 @@ "dyndns_unavailable": "Domain {domain:s} is not available.", "edit_group_not_allowed": "You are not allowed to edit the group {group:s}", "edit_permission_with_group_all_users_not_allowed": "You are not allowed to edit permission for group 'all_users', use 'yunohost user permission clear APP' or 'yunohost user permission add APP -u USER' instead.", + "error_when_removing_sftpuser_group": "Error when trying remove sftpusers group", "executing_command": "Executing command '{command:s}'…", "executing_script": "Executing script '{script:s}'…", "extracting": "Extracting…", @@ -333,9 +334,15 @@ "migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", + "migration_0009_backup_before_migration": "Make a backup of LDAP and apps settings before the migration", + "migration_0009_can_not_backup_before_migration": "The backup of the system before the migration failled. Migration failed. Error : {error:s}", "migration_0009_create_group": "Create group for each user.", "migration_0009_done": "Migration sucess. You are now able to use groups of user.", + "migration_0009_failled": "Migration failed.", + "migration_0009_LDAP_update_failled": "LDAP update failled. Error : {error:s}", "migration_0009_migrate_permission": "Migrate permission from apps settings to LDAP", + "migration_0009_migration_failled_try_rollback": "Migration failed. Try to restore the system. Error : {error:s}", + "migration_0009_rollback_success": "System restored.", "migration_0009_update_LDAP_database": "Update LDAP database for groups and permission support", "migration_0009_update_LDAP_schema": "Update LDAP schema", "migrations_backward": "Migrating backward.", diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 5767e20d5..2dee0b070 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -1,4 +1,7 @@ import yaml +import time +import os +import shutil from moulinette import m18n from moulinette.core import init_authenticator @@ -23,8 +26,8 @@ def migrate_LDAP_db(auth): logger.info(m18n.n("migration_0009_update_LDAP_database")) try: auth.remove('cn=sftpusers,ou=groups') - except Exception as e: - logger.warn("Error when trying remove sftpusers group") + except: + logger.warn(m18n.n("error_when_removing_sftpuser_group")) with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f: ldap_map = yaml.load(f) @@ -39,16 +42,18 @@ def migrate_LDAP_db(auth): for rdn, attr_dict in ldap_map['depends_children'].items(): auth.add(rdn, attr_dict) except Exception as e: - raise YunohostError("LDAP_update_failled") + raise YunohostError("migration_0009_LDAP_update_failled", error=e) logger.info(m18n.n("migration_0009_create_group")) - #Create group for each yunohost user + #Create a group for each yunohost user user_list = auth.search('ou=users,dc=yunohost,dc=org', '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', ['uid', 'uidNumber']) for user_info in user_list: username = user_info['uid'][0] + auth.update('uid=%s,ou=users' % username, + {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) user_group_add(auth, username, gid=user_info['uidNumber'][0], sync_perm=False) user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=False) @@ -88,6 +93,19 @@ class MyMigration(Migration): required = True def migrate(self): + # Backup LDAP and the apps settings before to do the migration + logger.info(m18n.n("migration_0009_backup_before_migration")) + try: + backup_folder = "/home/yunohost.backup/premigration/" + time.strftime('%Y%m%d-%H%M%S', time.gmtime()) + os.makedirs(backup_folder, 0o750) + os.system("systemctl stop slapd") + os.system("cp -r --preserve /etc/ldap %s/ldap_config" % backup_folder) + os.system("cp -r --preserve /var/lib/ldap %s/ldap_db" % backup_folder) + os.system("cp -r --preserve /etc/yunohost/apps %s/apps_settings" % backup_folder) + os.system("systemctl start slapd") + except Exception as e: + raise YunohostError("migration_0009_can_not_backup_before_migration", error=e) + # Update LDAP schema restart slapd logger.info(m18n.n("migration_0009_update_LDAP_schema")) service_regen_conf(names=['slapd'], force=True) @@ -99,11 +117,26 @@ class MyMigration(Migration): 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) - #Update LDAP database - migrate_LDAP_db(auth) + try: + #Update LDAP database + migrate_LDAP_db(auth) - # Migrate permission - migrate_app_permission(auth) + # Migrate permission + migrate_app_permission(auth) + + permission_sync_to_user(auth) + except Exception as e: + logger.warn(m18n.n("migration_0009_migration_failled_try_rollback", error=e)) + os.system("systemctl stop slapd") + os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config + os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) + os.system("cp -r --preserve %s/ldap_db/. /var/lib/ldap/" % backup_folder) + os.system("cp -r --preserve %s/apps_settings/. /etc/yunohost/apps/" % backup_folder) + os.system("systemctl start slapd") + os.system("rm -r " + backup_folder) + logger.info(m18n.n("migration_0009_rollback_success")) + raise YunohostError("migration_0009_failled") + + os.system("rm -r " + backup_folder) - permission_sync_to_user(auth) logger.info(m18n.n("migration_0009_done")) From efffff750e47f842bbddfdd4a997cfc4aab2040e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 28 Dec 2018 15:37:24 +0100 Subject: [PATCH 107/721] Implement Tests for groups and permissions --- src/yunohost/tests/test_backuprestore.py | 7 + src/yunohost/tests/test_permission.py | 316 +++++++++++++++++++++++ src/yunohost/tests/test_user-group.py | 210 +++++++++++++++ 3 files changed, 533 insertions(+) create mode 100644 src/yunohost/tests/test_permission.py create mode 100644 src/yunohost/tests/test_user-group.py diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 5775e1612..3560181bc 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -13,6 +13,7 @@ from yunohost.app import _is_installed from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError +from yunohost.user import user_permission_list # Get main domain maindomain = "" @@ -535,6 +536,7 @@ def _test_backup_and_restore_app(app): # Uninstall the app app_remove(auth, app) assert not app_is_installed(app) + assert app not in user_permission_list(auth)['permissions'] # Restore the app backup_restore(auth, system=None, name=archives[0], @@ -542,6 +544,11 @@ def _test_backup_and_restore_app(app): assert app_is_installed(app) + # Check permission + per_list = user_permission_list(auth)['permissions'] + assert app in per_list + assert "main" in per_list[app] + # # Some edge cases # # diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py new file mode 100644 index 000000000..c6b7c4129 --- /dev/null +++ b/src/yunohost/tests/test_permission.py @@ -0,0 +1,316 @@ +import pytest + +from moulinette.core import init_authenticator, MoulinetteError +from yunohost.app import app_install, app_remove, app_change_url +from yunohost.user import user_list, user_create, user_permission_list, user_delete, user_group_list, user_group_delete, user_permission_add, user_permission_remove, user_permission_clear +from yunohost.permission import permission_add, permission_update, permission_remove +from yunohost.domain import _get_maindomain +from yunohost.utils.error import YunohostError + +# Get main domain +maindomain = _get_maindomain() + +# Instantiate LDAP Authenticator +AUTH_IDENTIFIER = ('ldap', 'as-root') +AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} + +auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) + +def clean_user_groups_permission(): + for u in user_list(auth)['users']: + user_delete(auth, u) + + for g in user_group_list(auth)['groups']: + if g != "all_users": + user_group_delete(auth, g) + + for a, per in user_permission_list(auth)['permissions'].items(): + if a in ['wiki', 'blog', 'site']: + for p in per: + permission_remove(auth, a, p, force=True, sync_perm=False) + +def setup_function(function): + clean_user_groups_permission() + + user_create(auth, "alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") + user_create(auth, "bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") + permission_add(auth, "wiki", "main", [maindomain + "/wiki"], sync_perm=False) + permission_add(auth, "blog", "main", sync_perm=False) + + user_permission_add(auth, ["blog"], "main", group="alice") + +def teardown_function(function): + clean_user_groups_permission() + try: + app_remove(auth, "permissions_app") + except: + pass + +# +# List functions +# + +def test_list_permission(): + res = user_permission_list(auth)['permissions'] + + assert "wiki" in res + assert "main" in res['wiki'] + assert "blog" in res + assert "main" in res['blog'] + assert "mail" in res + assert "main" in res['mail'] + assert "metronome" in res + assert "main" in res['metronome'] + assert ["all_users"] == res['wiki']['main']['allowed_groups'] + assert ["alice"] == res['blog']['main']['allowed_groups'] + assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users']) + assert ["alice"] == res['blog']['main']['allowed_users'] + assert [maindomain + "/wiki"] == res['wiki']['main']['URL'] + +# +# Create - Remove functions +# + +def test_add_permission_1(): + permission_add(auth, "site", "test") + + res = user_permission_list(auth)['permissions'] + assert "site" in res + assert "test" in res['site'] + assert "all_users" in res['site']['test']['allowed_groups'] + assert set(["alice", "bob"]) == set(res['site']['test']['allowed_users']) + +def test_add_permission_2(): + permission_add(auth, "site", "main", default_allow=False) + + res = user_permission_list(auth)['permissions'] + assert "site" in res + assert "main" in res['site'] + assert [] == res['site']['main']['allowed_groups'] + assert [] == res['site']['main']['allowed_users'] + +def test_remove_permission(): + permission_remove(auth, "wiki", "main", force=True) + + res = user_permission_list(auth)['permissions'] + assert "wiki" not in res + +# +# Error on create - remove function +# + +def test_add_bad_permission(): + # Create permission with same name + with pytest.raises(YunohostError): + permission_add(auth, "wiki", "main") + +def test_remove_bad_permission(): + # Remove not existant permission + with pytest.raises(MoulinetteError): + permission_remove(auth, "non_exit", "main", force=True) + + res = user_permission_list(auth)['permissions'] + assert "wiki" in res + assert "main" in res['wiki'] + assert "blog" in res + assert "main" in res['blog'] + assert "mail" in res + assert "main" in res ['mail'] + assert "metronome" in res + assert "main" in res['metronome'] + +def test_remove_main_permission(): + with pytest.raises(YunohostError): + permission_remove(auth, "blog", "main") + + res = user_permission_list(auth)['permissions'] + assert "mail" in res + assert "main" in res['mail'] + +# +# Update functions +# + +# user side functions + +def test_allow_first_group(): + # Remove permission to all_users and define per users + user_permission_add(auth, ["wiki"], "main", group="alice") + + res = user_permission_list(auth)['permissions'] + assert ['alice'] == res['wiki']['main']['allowed_users'] + assert ['alice'] == res['wiki']['main']['allowed_groups'] + +def test_allow_other_group(): + # Allow new user in a permission + user_permission_add(auth, ["blog"], "main", group="bob") + + res = user_permission_list(auth)['permissions'] + assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users']) + assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_groups']) + +def test_disallow_group_1(): + # Disallow a user in a permission + user_permission_remove(auth, ["blog"], "main", group="alice") + + res = user_permission_list(auth)['permissions'] + assert [] == res['blog']['main']['allowed_users'] + assert [] == res['blog']['main']['allowed_groups'] + +def test_allow_group_1(): + # Allow a user when he is already allowed + user_permission_add(auth, ["blog"], "main", group="alice") + + res = user_permission_list(auth)['permissions'] + assert ["alice"] == res['blog']['main']['allowed_users'] + assert ["alice"] == res['blog']['main']['allowed_groups'] + +def test_disallow_group_1(): + # Disallow a user when he is already disallowed + user_permission_remove(auth, ["blog"], "main", group="bob") + + res = user_permission_list(auth)['permissions'] + assert ["alice"] == res['blog']['main']['allowed_users'] + assert ["alice"] == res['blog']['main']['allowed_groups'] + +def test_reset_permission(): + # Reset permission + user_permission_remove(auth, ["blog"], "main", group="bob") + + res = user_permission_list(auth)['permissions'] + assert ["alice"] == res['blog']['main']['allowed_users'] + assert ["alice"] == res['blog']['main']['allowed_groups'] + +# internal functions + +def test_add_url_1(): + # Add URL in permission which hasn't any URL defined + permission_update(auth, "blog", "main", add_url=[maindomain + "/testA"]) + + res = user_permission_list(auth)['permissions'] + assert [maindomain + "/testA"] == res['blog']['main']['URL'] + +def test_add_url_2(): + # Add a second URL in a permission + permission_update(auth, "wiki", "main", add_url=[maindomain + "/testA"]) + + res = user_permission_list(auth)['permissions'] + assert set([maindomain + "/testA", maindomain + "/wiki"]) == set(res['wiki']['main']['URL']) + +def test_remove_url_1(): + permission_update(auth, "wiki", "main", remove_url=[maindomain + "/wiki"]) + + res = user_permission_list(auth)['permissions'] + assert 'URL' not in res['wiki']['main'] + +def test_add_url_3(): + # Add a url already added + permission_update(auth, "wiki", "main", add_url=[maindomain + "/wiki"]) + + res = user_permission_list(auth)['permissions'] + assert [maindomain + "/wiki"] == res['wiki']['main']['URL'] + +def test_remove_url_2(): + # Remove a url not added (with a permission which contain some URL) + permission_update(auth, "wiki", "main", remove_url=[maindomain + "/not_exist"]) + + res = user_permission_list(auth)['permissions'] + assert [maindomain + "/wiki"] == res['wiki']['main']['URL'] + +def test_remove_url_2(): + # Remove a url not added (with a permission which contain no URL) + permission_update(auth, "blog", "main", remove_url=[maindomain + "/not_exist"]) + + res = user_permission_list(auth)['permissions'] + assert 'URL' not in res['blog']['main'] + +# +# Error on update function +# + +def test_disallow_bad_group_1(): + # Disallow a group when the group all_users is allowed + with pytest.raises(YunohostError): + user_permission_remove(auth, "wiki", "main", group="alice") + + res = user_permission_list(auth)['permissions'] + assert ["all_users"] == res['wiki']['main']['allowed_groups'] + assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users']) + +def test_allow_bad_user(): + # Allow a non existant group + with pytest.raises(YunohostError): + user_permission_add(auth, ["blog"], "main", group="not_exist") + + res = user_permission_list(auth)['permissions'] + assert ["alice"] == res['blog']['main']['allowed_groups'] + assert ["alice"] == res['blog']['main']['allowed_users'] + +def test_disallow_bad_group_2(): + # Disallow a non existant group + with pytest.raises(YunohostError): + user_permission_remove(auth, ["blog"], "main", group="not_exist") + + res = user_permission_list(auth)['permissions'] + assert ["alice"] == res['blog']['main']['allowed_groups'] + assert ["alice"] == res['blog']['main']['allowed_users'] + +def test_allow_bad_permission_1(): + # Allow a user to a non existant permission + with pytest.raises(YunohostError): + user_permission_add(auth, ["wiki"], "not_exit", group="alice") + +def test_allow_bad_permission_2(): + # Allow a user to a non existant permission + with pytest.raises(YunohostError): + user_permission_add(auth, ["not_exit"], "main", group="alice") + +# +# Application interaction +# + +def test_install_app(): + app_install(auth, "./tests/apps/permissions_app_ynh", + args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + + res = user_permission_list(auth)['permissions'] + assert "permissions_app" in res + assert "main" in res['permissions_app'] + assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL'] + assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL'] + assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL'] + + assert ["all_users"] == res['permissions_app']['main']['allowed_groups'] + assert set(["alice", "bob"]) == set(res['permissions_app']['main']['allowed_users']) + + assert ["alice"] == res['permissions_app']['admin']['allowed_groups'] + assert ["alice"] == res['permissions_app']['admin']['allowed_users'] + + assert ["all_users"] == res['permissions_app']['dev']['allowed_groups'] + assert set(["alice", "bob"]) == set(res['permissions_app']['dev']['allowed_users']) + +def test_remove_app(): + app_install(auth, "./tests/apps/permissions_app_ynh", + args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + app_remove(auth, "permissions_app") + + res = user_permission_list(auth)['permissions'] + assert "permissions_app" not in res + +def test_change_url(): + app_install(auth, "./tests/apps/permissions_app_ynh", + args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True) + + res = user_permission_list(auth)['permissions'] + assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL'] + assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL'] + assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL'] + + app_change_url(auth, "permissions_app", maindomain, "/newchangeurl") + + res = user_permission_list(auth)['permissions'] + assert [maindomain + "/newchangeurl"] == res['permissions_app']['main']['URL'] + assert [maindomain + "/newchangeurl/admin"] == res['permissions_app']['admin']['URL'] + assert [maindomain + "/newchangeurl/dev"] == res['permissions_app']['dev']['URL'] diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py new file mode 100644 index 000000000..351a3b432 --- /dev/null +++ b/src/yunohost/tests/test_user-group.py @@ -0,0 +1,210 @@ +import pytest + +from moulinette.core import init_authenticator, MoulinetteError +from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_add, user_group_delete, user_group_update, user_group_info +from yunohost.domain import _get_maindomain +from yunohost.utils.error import YunohostError + +# Get main domain +maindomain = _get_maindomain() + +# Instantiate LDAP Authenticator +AUTH_IDENTIFIER = ('ldap', 'as-root') +AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} + +auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) + +def clean_user_groups(): + for u in user_list(auth)['users']: + user_delete(auth, u) + + for g in user_group_list(auth)['groups']: + if g != "all_users": + user_group_delete(auth, g) + +def setup_function(function): + clean_user_groups() + + user_create(auth, "alice", "Alice", "White", "alice@" + maindomain, "test123Ynh") + user_create(auth, "bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh") + user_create(auth, "jack", "Jack", "Black", "jack@" + maindomain, "test123Ynh") + + user_group_add(auth, "dev") + user_group_add(auth, "apps") + user_group_update(auth, "dev", add_user=["alice"]) + user_group_update(auth, "apps", add_user=["bob"]) + +def teardown_function(function): + clean_user_groups() + +# +# List functions +# + +def test_list_users(): + res = user_list(auth)['users'] + + assert "alice" in res + assert "bob" in res + assert "jack" in res + +def test_list_groups(): + res = user_group_list(auth)['groups'] + + assert "all_users" in res + assert "alice" in res + assert "bob" in res + assert "jack" in res + for u in ["alice", "bob", "jack"]: + assert u in res + assert u in res[u]['members'] + assert u in res["all_users"]['members'] + +# +# Create - Remove functions +# + +def test_create_user(): + user_create(auth, "albert", "Albert", "Good", "alber@" + maindomain, "test123Ynh") + + group_res = user_group_list(auth)['groups'] + assert "albert" in user_list(auth)['users'] + assert "albert" in group_res + assert "albert" in group_res['albert']['members'] + assert "albert" in group_res['all_users']['members'] + +def test_del_user(): + user_delete(auth, "alice") + + group_res = user_group_list(auth)['groups'] + assert "alice" not in user_list(auth) + assert "alice" not in group_res + assert "alice" not in group_res['all_users']['members'] + +def test_add_group(): + user_group_add(auth, "adminsys") + + group_res = user_group_list(auth)['groups'] + assert "adminsys" in group_res + assert "members" not in group_res['adminsys'] + +def test_del_group(): + user_group_delete(auth, "dev") + + group_res = user_group_list(auth)['groups'] + assert "dev" not in group_res + +# +# Error on create / remove function +# + +def test_add_bad_user_1(): + # Check email already exist + with pytest.raises(MoulinetteError): + user_create(auth, "alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh") + +def test_add_bad_user_2(): + # Check to short password + with pytest.raises(MoulinetteError): + user_create(auth, "other", "Alice", "White", "other@" + maindomain, "12") + +def test_add_bad_user_3(): + # Check user already exist + with pytest.raises(MoulinetteError): + user_create(auth, "alice", "Alice", "White", "other@" + maindomain, "test123Ynh") + +def test_del_bad_user_1(): + # Check user not found + with pytest.raises(MoulinetteError): + user_delete(auth, "not_exit") + +def test_add_bad_group_1(): + # Check groups already exist with special group "all_users" + with pytest.raises(YunohostError): + user_group_add(auth, "all_users") + +def test_add_bad_group_2(): + # Check groups already exist (for standard groups) + with pytest.raises(MoulinetteError): + user_group_add(auth, "dev") + +def test_del_bad_group_1(): + # Check not allowed to remove this groups + with pytest.raises(YunohostError): + user_group_delete(auth, "all_users") + +def test_del_bad_group_2(): + # Check groups not found + with pytest.raises(MoulinetteError): + user_group_delete(auth, "not_exit") + +# +# Update function +# + +def test_update_user_1(): + user_update(auth, "alice", firstname="NewName", lastname="NewLast") + + info = user_info(auth, "alice") + assert "NewName" == info['firstname'] + assert "NewLast" == info['lastname'] + +def test_update_group_1(): + user_group_update(auth, "dev", add_user=["bob"]) + + group_res = user_group_list(auth)['groups'] + assert set(["alice", "bob"]) == set(group_res['dev']['members']) + +def test_update_group_2(): + # Try to add a user in a group when the user is already in + user_group_update(auth, "apps", add_user=["bob"]) + + group_res = user_group_list(auth)['groups'] + assert ["bob"] == group_res['apps']['members'] + +def test_update_group_3(): + # Try to remove a user in a group + user_group_update(auth, "apps", remove_user=["bob"]) + + group_res = user_group_list(auth)['groups'] + assert "members" not in group_res['apps'] + +def test_update_group_4(): + # Try to remove a user in a group when it is not already in + user_group_update(auth, "apps", remove_user=["jack"]) + + group_res = user_group_list(auth)['groups'] + assert ["bob"] == group_res['apps']['members'] + +# +# Error on update functions +# + +def test_bad_update_user_1(): + # Check user not found + with pytest.raises(YunohostError): + user_update(auth, "not_exit", firstname="NewName", lastname="NewLast") + +def bad_update_group_1(): + # Check groups not found + with pytest.raises(YunohostError): + user_group_update(auth, "not_exit", add_user=["alice"]) + +def test_bad_update_group_2(): + # Check remove user in groups "all_users" not allowed + with pytest.raises(YunohostError): + user_group_update(auth, "all_users", remove_user=["alice"]) + +def test_bad_update_group_3(): + # Check remove user in it own group not allowed + with pytest.raises(YunohostError): + user_group_update(auth, "alice", remove_user=["alice"]) + +def test_bad_update_group_1(): + # Check add bad user in group + with pytest.raises(YunohostError): + user_group_update(auth, "dev", add_user=["not_exist"]) + + assert "not_exist" not in user_group_list(auth)["groups"]["dev"] From 5a6a85ab077dad588c55ad37b920ae0171514a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 9 Jan 2019 12:04:05 +0100 Subject: [PATCH 108/721] Add check LDAP integrity --- src/yunohost/tests/test_backuprestore.py | 7 ++ src/yunohost/tests/test_permission.py | 94 +++++++++++++++++++++++- src/yunohost/tests/test_user-group.py | 8 ++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 3560181bc..508ea890b 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -14,6 +14,7 @@ from yunohost.backup import backup_create, backup_restore, backup_list, backup_i from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError from yunohost.user import user_permission_list +from yunohost.tests.test_permission import check_LDAP_db_integrity # Get main domain maindomain = "" @@ -91,6 +92,12 @@ def teardown_function(function): shutil.rmtree("/opt/test_backup_output_directory") +@pytest.fixture(autouse=True) +def check_LDAP_db_integrity_call(): + check_LDAP_db_integrity() + yield + check_LDAP_db_integrity() + # # Helpers # # diff --git a/src/yunohost/tests/test_permission.py b/src/yunohost/tests/test_permission.py index c6b7c4129..2ed84f937 100644 --- a/src/yunohost/tests/test_permission.py +++ b/src/yunohost/tests/test_permission.py @@ -48,6 +48,94 @@ def teardown_function(function): except: pass +@pytest.fixture(autouse=True) +def check_LDAP_db_integrity_call(): + check_LDAP_db_integrity() + yield + check_LDAP_db_integrity() + +def check_LDAP_db_integrity(): + # Here we check that all attributes in all object are sychronized. + # Here is the list of attributes per object: + # user : memberOf, permission + # group : member, permission + # permission : groupPermission, inheritPermission + # + # The idea is to check that all attributes on all sides of object are sychronized. + # One part should be done automatically by the "memberOf" overlay of LDAP. + # The other part is done by the the "permission_sync_to_user" function of the permission module + + user_search = auth.search('ou=users,dc=yunohost,dc=org', + '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', + ['uid', 'memberOf', 'permission']) + group_search = auth.search('ou=groups,dc=yunohost,dc=org', + '(objectclass=groupOfNamesYnh)', + ['cn', 'member', 'memberUid', 'permission']) + permission_search = auth.search('ou=permission,dc=yunohost,dc=org', + '(objectclass=permissionYnh)', + ['cn', 'groupPermission', 'inheritPermission', 'memberUid']) + + user_map = {u['uid'][0]: u for u in user_search} + group_map = {g['cn'][0]: g for g in group_search} + permission_map = {p['cn'][0]: p for p in permission_search} + + for user in user_search: + user_dn = 'uid=' + user['uid'][0] + ',ou=users,dc=yunohost,dc=org' + group_list = [m.split("=")[1].split(",")[0] for m in user['memberOf']] + permission_list = [] + if 'permission' in user: + permission_list = [m.split("=")[1].split(",")[0] for m in user['permission']] + + for group in group_list: + assert user_dn in group_map[group]['member'] + for permission in permission_list: + assert user_dn in permission_map[permission]['inheritPermission'] + + for permission in permission_search: + permission_dn = 'cn=' + permission['cn'][0] + ',ou=permission,dc=yunohost,dc=org' + user_list = [] + group_list = [] + if 'inheritPermission' in permission: + user_list = [m.split("=")[1].split(",")[0] for m in permission['inheritPermission']] + assert set(user_list) == set(permission['memberUid']) + if 'groupPermission' in permission: + group_list = [m.split("=")[1].split(",")[0] for m in permission['groupPermission']] + + for user in user_list: + assert permission_dn in user_map[user]['permission'] + for group in group_list: + assert permission_dn in group_map[group]['permission'] + if 'member' in group_map[group]: + user_list_in_group = [m.split("=")[1].split(",")[0] for m in group_map[group]['member']] + assert set(user_list_in_group) <= set(user_list) + + for group in group_search: + group_dn = 'cn=' + group['cn'][0] + ',ou=groups,dc=yunohost,dc=org' + user_list = [] + permission_list = [] + if 'member' in group: + user_list = [m.split("=")[1].split(",")[0] for m in group['member']] + if group['cn'][0] in user_list: + # If it's the main group of the user it's normal that it is not in the memberUid + g_list = list(user_list) + g_list.remove(group['cn'][0]) + if 'memberUid' in group: + assert set(g_list) == set(group['memberUid']) + else: + assert g_list == [] + else: + assert set(user_list) == set(group['memberUid']) + if 'permission' in group: + permission_list = [m.split("=")[1].split(",")[0] for m in group['permission']] + + for user in user_list: + assert group_dn in user_map[user]['memberOf'] + for permission in permission_list: + assert group_dn in permission_map[permission]['groupPermission'] + if 'inheritPermission' in permission_map: + allowed_user_list = [m.split("=")[1].split(",")[0] for m in permission_map[permission]['inheritPermission']] + assert set(user_list) <= set(allowed_user_list) + # # List functions # @@ -177,11 +265,11 @@ def test_disallow_group_1(): def test_reset_permission(): # Reset permission - user_permission_remove(auth, ["blog"], "main", group="bob") + user_permission_clear(auth, ["blog"], "main") res = user_permission_list(auth)['permissions'] - assert ["alice"] == res['blog']['main']['allowed_users'] - assert ["alice"] == res['blog']['main']['allowed_groups'] + assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users']) + assert ["all_users"] == res['blog']['main']['allowed_groups'] # internal functions diff --git a/src/yunohost/tests/test_user-group.py b/src/yunohost/tests/test_user-group.py index 351a3b432..48b56884e 100644 --- a/src/yunohost/tests/test_user-group.py +++ b/src/yunohost/tests/test_user-group.py @@ -4,6 +4,7 @@ from moulinette.core import init_authenticator, MoulinetteError from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_add, user_group_delete, user_group_update, user_group_info from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError +from yunohost.tests.test_permission import check_LDAP_db_integrity # Get main domain maindomain = _get_maindomain() @@ -39,6 +40,12 @@ def setup_function(function): def teardown_function(function): clean_user_groups() +@pytest.fixture(autouse=True) +def check_LDAP_db_integrity_call(): + check_LDAP_db_integrity() + yield + check_LDAP_db_integrity() + # # List functions # @@ -187,6 +194,7 @@ def test_bad_update_user_1(): with pytest.raises(YunohostError): user_update(auth, "not_exit", firstname="NewName", lastname="NewLast") + def bad_update_group_1(): # Check groups not found with pytest.raises(YunohostError): From 9109850fb06b88c07bf3c493f52d7aad9487a81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 9 Jan 2019 22:56:41 +0100 Subject: [PATCH 109/721] Fix mailbox info when user not allowed to access to mail --- locales/en.json | 1 + src/yunohost/user.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/locales/en.json b/locales/en.json index 45c1fbb90..6ab780feb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -288,6 +288,7 @@ "mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'", "mail_domain_unknown": "Unknown mail address domain '{domain:s}'", "mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'", + "mailbox_disabled": "Mailbox disabled for user {user:s}", "mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space", "mail_unavailable": "This email address is reserved and shall be automatically allocated to the very first user", "maindomain_change_failed": "Unable to change the main domain", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index b800c9b0e..5e58909f7 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -437,6 +437,8 @@ def user_info(auth, username): if service_status("dovecot")["status"] != "running": logger.warning(m18n.n('mailbox_used_space_dovecot_down')) + elif not user_permission_list(auth, app="mail", permission="main", username=username)['permissions']: + logger.warning(m18n.n('mailbox_disabled', user=username)) else: cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] cmd_result = subprocess.check_output(cmd, stderr=subprocess.STDOUT, From 146c4093b4d9c96606f3d234e5ec01603db24d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 9 Jan 2019 22:58:49 +0100 Subject: [PATCH 110/721] Fix Typo Permission --- locales/en.json | 2 +- src/yunohost/permission.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6ab780feb..5ddf14590 100644 --- a/locales/en.json +++ b/locales/en.json @@ -411,7 +411,7 @@ "permission_already_clear": "Permission '{permission:s}' already clear for app {app:s}", "permission_already_exist": "Permission '{permission:s}' for app {app:s} already exist", "permission_created": "Permission '{permission:s}' for app {app:s} created", - "premission_creation_failled": "Permission creation failed", + "permission_creation_failled": "Permission creation failed", "permission_deleted": "Permission '{permission:s}' for app {app:s} deleted", "permission_deletion_failed": "Permission '{permission:s}' for app {app:s} deletion failed", "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index b7f2b9949..f4bd771a5 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -364,7 +364,7 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al logger.success(m18n.n('permission_created', permission=permission, app=app)) return user_permission_list(auth, app, permission) - raise YunohostError('premission_creation_failled') + raise YunohostError('permission_creation_failled') @is_unit_operation(['permission','app']) From f999f85507ae94fb27aa5d882401a5ce213f352e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 9 Jan 2019 22:59:51 +0100 Subject: [PATCH 111/721] Sync permission at the end of backup --- src/yunohost/backup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index a2001458b..c22182a45 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -910,11 +910,12 @@ class RestoreManager(): service_regen_conf(names=['slapd'], force=True) migration_0009.migrate_LDAP_db(auth) - def clean(self): + def clean(self, auth): """ End a restore operations by cleaning the working directory and regenerate ssowat conf (if some apps were restored) """ + from permission import permission_sync_to_user successfull_apps = self.targets.list("apps", include=["Success", "Warning"]) @@ -923,6 +924,8 @@ class RestoreManager(): # ldap restore hooks os.system('sudo yunohost app ssowatconf') + permission_sync_to_user(auth, force=True) + if os.path.ismount(self.work_dir): ret = subprocess.call(["umount", self.work_dir]) if ret != 0: @@ -1135,7 +1138,7 @@ class RestoreManager(): self._migrate_system_if_needed(auth) self._restore_apps(auth) finally: - self.clean() + self.clean(auth) def _patch_backup_csv_file(self): """ From e938c6d309d9e6d109ff320496aa77cfab7db640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 9 Jan 2019 23:02:49 +0100 Subject: [PATCH 112/721] Improve sync permission function --- src/yunohost/permission.py | 71 +++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index f4bd771a5..77f9adbfe 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -444,49 +444,64 @@ def permission_remove(operation_logger, auth, app, permission, force=False, sync logger.success(m18n.n('permission_deleted', permission=permission, app=app)) -def permission_sync_to_user(auth): +def permission_sync_to_user(auth, force=False): """ Sychronise the inheritPermission attribut in the permission object from the user<->group link and the group<->permission link + + Keyword argument: + force -- Force to recreate all attributes. Used generally with the backup which wich use "slapadd" which don't use the memberOf overlay. + Note that by removing all value and adding a new time, we force the overlay to update all attributes """ + # Note that a LDAP operation with the same value that is in LDAP crash SLAP. + # So we need to check before each ldap operation that we really change something in LDAP import os from yunohost.app import app_ssowatconf permission_attrs = [ 'cn', 'member', - 'permission', ] group_info = auth.search('ou=groups,dc=yunohost,dc=org', '(objectclass=groupOfNamesYnh)', permission_attrs) - user_permission={} - - for group in group_info: - if 'permission' not in group: - continue - if not 'member' in group: - continue - for permission in group['permission']: - permission = permission.split("=")[1].split(",")[0] - if not permission in user_permission: - user_permission[permission] = set() - for member in group['member']: - user_permission[permission].add(member) + group_info = {g['cn'][0]: g for g in group_info} for per in auth.search('ou=permission,dc=yunohost,dc=org', - '(objectclass=permissionYnh)', ['cn', 'inheritPermission']): - if per['cn'][0] in user_permission: - val = set(user_permission[per['cn'][0]]) - else: - # If the new value and the old value à empty nothing to do - if not 'inheritPermission' in per: - continue - val = set() - if 'inheritPermission' in per and val == set(per['inheritPermission']): + '(objectclass=permissionYnh)', + ['cn', 'inheritPermission', 'groupPermission', 'memberUid']): + if 'groupPermission' not in per: continue - uid_val = [v.split("=")[1].split(",")[0] for v in val] - inheritPermission = {'inheritPermission': val, 'memberUid': uid_val} - if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): - raise YunohostError('permission_update_failed') + user_permission = set() + for group in per['groupPermission']: + group = group.split("=")[1].split(",")[0] + if 'member' not in group_info[group]: + continue + for user in group_info[group]['member']: + user_permission.add(user) + + if 'inheritPermission' not in per: + per['inheritPermission'] = [] + if 'memberUid' not in per: + per['memberUid'] = [] + + uid_val = [v.split("=")[1].split(",")[0] for v in user_permission] + if user_permission == set(per['inheritPermission']) and set(uid_val) == set(per['memberUid']) and not force: + continue + inheritPermission = {'inheritPermission': user_permission, 'memberUid': uid_val} + if force: + if per['groupPermission']: + if not auth.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': []}): + raise YunohostError('permission_update_failed_clear') + if not auth.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': per['groupPermission']}): + raise YunohostError('permission_update_failed_populate') + if per['inheritPermission']: + if not auth.update('cn=%s,ou=permission' % per['cn'][0], {'inheritPermission': []}): + raise YunohostError('permission_update_failed_clear') + if user_permission: + if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): + raise YunohostError('permission_update_failed') + else: + if not auth.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission): + raise YunohostError('permission_update_failed') logger.success(m18n.n('permission_generated')) app_ssowatconf(auth) From e199b03d7d2165af814be1df5b0594efbd625d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 11 Jan 2019 17:22:03 +0100 Subject: [PATCH 113/721] Use nscd command to invalide group informations --- src/yunohost/permission.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 77f9adbfe..4be463e50 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -507,4 +507,5 @@ def permission_sync_to_user(auth, force=False): app_ssowatconf(auth) # Reload unscd because if not the group is not updated in the system from LDAP - os.system('systemctl restart unscd') + os.system('nscd --invalidate=passwd') + os.system('nscd --invalidate=group') From 6054e4eb3cf29c7c0b0fa87c88aa63eed63e0995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 16 Jan 2019 00:00:54 +0100 Subject: [PATCH 114/721] Improve permission list --- src/yunohost/permission.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 4be463e50..421f1ff2f 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -61,13 +61,10 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N app = [app] if permission and not isinstance(permission, list): permission = [permission] + if not isinstance(username, list): + username = [username] if not isinstance(group, list): group = [group] - if isinstance(username, list): - group.extend(username) - else: - group.append(username) - group = filter(None, group) permissions = {} @@ -94,7 +91,9 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N continue if permission and not permission_name in permission: continue - if group and not set(group) & set(group_name): + if username[0] and not set(username) & set(user_name): + continue + if group[0] and not set(group) & set(group_name): continue if not app_name in permissions: From aebd0e8d784368474d984b2d8e5a587e5e8e686a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 17 Jan 2019 22:17:08 +0100 Subject: [PATCH 115/721] Update changelog for 3.4.1 --- debian/changelog | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/debian/changelog b/debian/changelog index 0e025d2da..48ea4b2a0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,22 @@ +yunohost (3.4.1) testing; urgency=low + + * [fix] `_run_service_command` not properly returning False if command fails (#616) + * [enh] Change git clone for gitlab working with branch (#615) + * [fix] Set owner of archives folder to 'admin' (#613) + * [enh] Add reload and restart actions to 'yunohost service' (#611) + * [fix] propagate --no-checks cert-install option to renew crontab (#610) + * [fix] Several issues with bootprompt (#609) + * [fix] Fix the way change_url updates the domain/path (#608) + * [fix] Repair tests (#607) + * [fix] Explicit dependance to iptables (1667ba1) + * [i18n] Tiny typographic changes (#612) + * [i18n] Improve translations for Hungarian, Esperanto, German + * Misc minor fixes and improvements. + + Thanks to all contributors (Aleks, Bram, J. Meggyeshazi, Jibec, Josué, M. Martin, P. Bourré, anubis) ! <3 + + -- Alexandre Aubin Thu, 17 Jan 2019 22:16:00 +0000 + yunohost (3.4.0) testing; urgency=low * Misc fixes (#601, #600, #593) From 2e460cb4d60b451795f09e9fd0cca70fa61df336 Mon Sep 17 00:00:00 2001 From: frju365 Date: Fri, 18 Jan 2019 17:31:28 +0100 Subject: [PATCH 116/721] Update yunohost_admin.conf --- .../templates/nginx/plain/yunohost_admin.conf | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index 3de66e3e6..ce7a5a773 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -13,10 +13,8 @@ server { server { # Disabling http2 for now as it's causing weird issues with curl - #listen 443 ssl http2 default_server; - #listen [::]:443 ssl http2 default_server; - listen 443 ssl default_server; - listen [::]:443 ssl default_server; + listen 443 ssl http2 default_server; + listen [::]:443 ssl http2 default_server; ssl_certificate /etc/yunohost/certs/yunohost.org/crt.pem; ssl_certificate_key /etc/yunohost/certs/yunohost.org/key.pem; @@ -25,11 +23,7 @@ server { # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 # (this doesn't work on jessie though ...?) - # ssl_ecdh_curve secp521r1:secp384r1:prime256v1; - - # As suggested by https://cipherli.st/ - ssl_ecdh_curve secp384r1; - + ssl_ecdh_curve secp521r1:secp384r1:prime256v1; ssl_prefer_server_ciphers on; # Ciphers with intermediate compatibility @@ -50,14 +44,14 @@ server { # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners # https://wiki.mozilla.org/Security/Guidelines/Web_Security # https://observatory.mozilla.org/ - add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; - add_header 'Referrer-Policy' 'same-origin'; - add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'"; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Download-Options noopen; - add_header X-Permitted-Cross-Domain-Policies none; - add_header X-Frame-Options "SAMEORIGIN"; + more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; + more_set_headers "Referrer-Policy : 'same-origin'"; + more_set_headers "Content-Security-Policy : upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'"; + more_set_headers "X-Content-Type-Options : nosniff"; + more_set_headers "X-XSS-Protection : 1; mode=block"; + more_set_headers "X-Download-Options : noopen"; + more_set_headers "X-Permitted-Cross-Domain-Policies : none"; + more_set_headers "X-Frame-Options : SAMEORIGIN"; location / { return 302 https://$http_host/yunohost/admin; From bd0eef1b366fa3810470c0de3ce6d5724b5e4b04 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 18 Jan 2019 17:54:26 +0100 Subject: [PATCH 117/721] Remove old comment about jessie --- data/templates/nginx/plain/yunohost_admin.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index ce7a5a773..5e7679b7d 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -22,7 +22,6 @@ server { ssl_session_cache shared:SSL:50m; # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 - # (this doesn't work on jessie though ...?) ssl_ecdh_curve secp521r1:secp384r1:prime256v1; ssl_prefer_server_ciphers on; From 4ceb2cbe1dbfb958a9dbd7797bb91a791a09b9d1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Jan 2019 17:15:39 +0100 Subject: [PATCH 118/721] Squashed 'src/yunohost/vendor/spectre-meltdown-checker/' changes from edebe4dc..d7d2e693 d7d2e693 fix: typo in bare metal detection (fixes #269) b0083d91 Remove unneeded volumes in Dockerfile (#266) 904a83c6 Fix Arch kernel image detection (#268) 906f54cf Improved hypervisor detection (#259) c45a06f4 Warn on missing kernel info (#265) 4a6fa070 Fix misdetection of files under Clear Linux (#264) c705afe7 bump to v0.40 401ccd4b Correct aarch64 KPTI dmesg message 55120839 Fix a typo in check_variant3_linux() f5106b3c update MCEDB from v83 to v84 (no actual change) 68289dae feat: add --update-builtin-mcedb to update the DB inside the script 3b2d5296 feat(l1tf): read & report ARCH_CAPABILITIES bit 3 (SKIP_VMENTRY_L1DFLUSH) cbb18cb6 fix(l1tf): properly detect status under Red Hat/CentOS kernels 299103a3 some fixes when script is not started as root dc5402b3 chore: speed optimization of hw check and indentation fixes 90c2ae5d feat: use the MCExtractor DB as the reference for the microcode versions 53d6a447 Fix detection of CVE-2018-3615 (L1TF_SGX) (#253) 297d890c fix ucode version check regression introduced by fbbb19f under BSD 0252e74f feat(bsd): implement CVE-2018-3620 and CVE-2018-3646 mitigation detection fbbb19f2 Fix cases where a CPU ucode version is not found in $procfs/cpuinfo. (#246) 1571a56c feat: add L1D flush cpuid feature bit detection 3cf91416 fix: don't display summary if no CVE was tested (e.g. --hw-only) bff38f1b BSD: add not-implemented-yet notice for Foreshadow-NG b419fe7c feat(variant4): properly detect SSBD under BSD f193484a chore: fix deprecated SPDX license identifier (#249) (#251) 349d77b3 Fix kernel detection when /lib/kernel exists on a distro (#252) e589ed7f fix: don't test SGX again in check_CVE_2018_3615, already done by is_cpu_vulnerable ae120628 fix: remove some harcoded /proc paths, use $procfs instead b44d2b54 chore: remove 'experimental' notice of Foreshadow from README 7b72c20f feat(l1tf): explode L1TF in its 3 distinct CVEs b48b2177 feat: Add Clear Linux Distro (#244) 8f31634d feat(batch): Add a batch short option for one line result (#243) 96798b19 chore: add SPDX GPL-3.0 license identifier (#245) 687ce1a7 fix: load cpuid module if absent even when /dev/cpu/0/cpuid is there 80e0db7c fix: don't show erroneous ucode version when latest version is unknown (fixes #238) e8890ffa feat(config): support for genkernel kernel config file (#239) b2f64e11 fix README after merge 42a3a61f Slightly improved Docker configuration (#230) afb36c51 Fix typo: 'RBS filling' => 'RSB filling' (#237) 0009c0d4 fix: --batch now implies --no-color to avoid colored warnings dd67fd94 feat: add FLUSH_CMD MSR availability detection (part of L1TF mitigation) 339ad317 fix: add missing l1tf CPU vulnerability display in hw section 794c5be1 feat: add optional git describe support to display inter-release version numbers a7afc585 fix several incorrect ucode version numbers fc1dffd0 feat: implement detection of latest known versions of intel microcodes e9426161 feat: initial support for L1TF 360be7b3 fix: hide arch_capabilities_msr_not_read warning under !intel 5f592578 bump to v0.39 92d59cbd chore: adjust some comments, add 2 missing inits 4747b932 feat: add detection of RSBA feature bit and adjust logic accordingly 860023a8 fix: ARCH MSR was not read correctly, preventing proper SSB_NO and RDCL_NO detection ab67a922 feat: read/write msr now supports msr-tools or perl as dd fallback f4592bf3 Add Arch armv5/armv7 kernel image location (#227) be15e476 chore: setting master to v0.38+ d3481d95 Add support for the kernel being within a btrfs subvolume (#226) 21af5611 bump to v0.38 cb740397 feat(arm32): add spectrev1 mitigation detection 84195689 change: default to --no-explain, use --explain to get detailed mitigation help b637681f fix: debug output: msg inaccuracy for ARM checks 9316c305 fix: armv8: models < 0xd07 are not vulnerable f9dd9d8c add guess for archlinuxarm aarch64 kernel image on raspberry pi 3 (#222) 0f0d103a fix: correctly init capabilities_ssb_no var in all cases b262c405 fix: remove spurious character after an else statement cc2910fb fix: read_cpuid: don't use iflag=skip_bytes for compat with old dd versions 30c4a1f6 arm64: cavium: Add CPU Implementer Cavium (#216) cf06636a fix: prometheus output: use printf for proper \n interpretation (#204) 60077c8d fix(arm): rewrite vuln logic from latest arm statement for Cortex A8 to A76 c181978d fix(arm): Updated arm cortex status (#209) 9a6406a9 chore: add docker support (#203) 5962d20b fix(variant4): whitelist from common.c::cpu_no_spec_store_bypass (#202) 17a34885 fix(help): add missing references to variants 3a & 4 (#201) e54e8b3e chore: remove warning in README, fix display indentation 39c778e3 fix(amd): AMD families 0x15-0x17 non-arch MSRs are a valid way to control SSB 2cde6e46 feat(ssbd): add detection of proper CPUID bits on AMD f4d51e7e fix(variant4): add another detection way for Red Hat kernel 85d46b27 feat(variant4): add more detailed explanations 61e02abd feat(variant3a): detect up to date microcode 114756fa fix(amd): not vulnerable to variant3a ea75969e fix(help): Update variant options in usage message (#200) ca391cbf fix(variant2): correctly detect IBRS/IBPB in SLES kernels 68af5c5f feat(variant4): detect SSBD-aware kernel 19be8f79 doc: update README with some info about variant3 and variant4 f75cc0bb feat(variant4): add sysfs mitigation hint and some explanation about the vuln f33d65ff feat(variant3a): add information about microcode-sufficient mitigation 725eaa8b feat(arm): adjust vulnerable ARM CPUs for variant3a and variant4 c6ee0358 feat(variant4): report SSB_NO CPUs as not vulnerable 22d0b203 fix(ssb_no): rename ssbd_no to ssb_no and fix shift 3062a841 fix(msg): add missing words 6a4318ad feat(variant3a/4): initial support for 2 new CVEs c1998618 fix(variant2): adjust detection for SLES kernels 7e4899bc ibrs can't be enabled on no ibrs cpu (#195) 5cc77741 Update spectre-meltdown-checker.sh 1c0f6d95 cpuid and msr module check 4acd0f64 Suggestion to change VM to a CPU with IBRS capability fb52dbe7 set master branch to v0.37+ git-subtree-dir: src/yunohost/vendor/spectre-meltdown-checker git-subtree-split: d7d2e6934ba08a2de2e2c80bb42936a60b884b78 --- Dockerfile | 7 + README.md | 59 +- docker-compose.yml | 15 + spectre-meltdown-checker.sh | 1941 +++++++++++++++++++++++++++++++---- 4 files changed, 1804 insertions(+), 218 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..93fe602ee --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM alpine:3.7 + +RUN apk --update --no-cache add kmod binutils grep perl + +COPY . /check + +ENTRYPOINT ["/check/spectre-meltdown-checker.sh"] diff --git a/README.md b/README.md index 4a9c71828..5b89c8dce 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ Spectre & Meltdown Checker ========================== -A shell script to tell if your system is vulnerable against the 3 "speculative execution" CVEs that were made public early 2018. +A shell script to tell if your system is vulnerable against the several "speculative execution" CVEs that were made public in 2018. +- CVE-2017-5753 [bounds check bypass] aka 'Spectre Variant 1' +- CVE-2017-5715 [branch target injection] aka 'Spectre Variant 2' +- CVE-2017-5754 [rogue data cache load] aka 'Meltdown' aka 'Variant 3' +- CVE-2018-3640 [rogue system register read] aka 'Variant 3a' +- CVE-2018-3639 [speculative store bypass] aka 'Variant 4' +- CVE-2018-3615 [L1 terminal fault] aka 'Foreshadow (SGX)' +- CVE-2018-3620 [L1 terminal fault] aka 'Foreshadow-NG (OS)' +- CVE-2018-3646 [L1 terminal fault] aka 'Foreshadow-NG (VMM)' Supported operating systems: - Linux (all versions, flavors and distros) @@ -39,6 +47,22 @@ chmod +x spectre-meltdown-checker.sh sudo ./spectre-meltdown-checker.sh ``` +### Run the script in a docker container + +#### With docker-compose + +```shell +docker-compose build +docker-compose run --rm spectre-meltdown-checker +``` + +#### Without docker-compose + +```shell +docker build -t spectre-meltdown-checker . +docker run --rm --privileged -v /boot:/boot:ro -v /dev/cpu:/dev/cpu:ro -v /lib/modules:/lib/modules:ro spectre-meltdown-checker +``` + ## Example of script output - Intel Haswell CPU running under Ubuntu 16.04 LTS @@ -74,7 +98,38 @@ sudo ./spectre-meltdown-checker.sh - Mitigation: updated kernel (with PTI/KPTI patches), updating the kernel is enough - Performance impact of the mitigation: low to medium -## Disclaimer +**CVE-2018-3640** rogue system register read (Variant 3a) + + - Impact: TBC + - Mitigation: microcode update only + - Performance impact of the mitigation: negligible + +**CVE-2018-3639** speculative store bypass (Variant 4) + + - Impact: software using JIT (no known exploitation against kernel) + - Mitigation: microcode update + kernel update making possible for affected software to protect itself + - Performance impact of the mitigation: low to medium + +**CVE-2018-3615** l1 terminal fault (Foreshadow-NG SGX) + + - Impact: Kernel & all software (any physical memory address in the system) + - Mitigation: microcode update + - Performance impact of the mitigation: negligible + +**CVE-2018-3620** l1 terminal fault (Foreshadow-NG SMM) + + - Impact: Kernel & System management mode + - Mitigation: updated kernel (with PTE inversion) + - Performance impact of the mitigation: negligible + +**CVE-2018-3646** l1 terminal fault (Foreshadow-NG VMM) + + - Impact: Virtualization software and Virtual Machine Monitors + - Mitigation: disable ept (extended page tables), disable hyper-threading (SMT), or + updated kernel (with L1d flush) + - Performance impact of the mitigation: low to significant + +## Understanding what this script does and doesn't This tool does its best to determine whether your system is immune (or has proper mitigations in place) for the collectively named "speculative execution" vulnerabilities. It doesn't attempt to run any kind of exploit, and can't guarantee that your system is secure, but rather helps you verifying whether your system has the known correct mitigations in place. However, some mitigations could also exist in your kernel that this script doesn't know (yet) how to detect, or it might falsely detect mitigations that in the end don't work as expected (for example, on backported or modified kernels). diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..c4024d680 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: '2' + +services: + spectre-meltdown-checker: + build: + context: ./ + dockerfile: ./Dockerfile + image: spectre-meltdown-checker:latest + container_name: spectre-meltdown-checker + privileged: true + network_mode: none + volumes: + - /boot:/boot:ro + - /dev/cpu:/dev/cpu:ro + - /lib/modules:/lib/modules:ro diff --git a/spectre-meltdown-checker.sh b/spectre-meltdown-checker.sh index 0f3c10575..9c1aa7191 100755 --- a/spectre-meltdown-checker.sh +++ b/spectre-meltdown-checker.sh @@ -1,4 +1,6 @@ #! /bin/sh +# SPDX-License-Identifier: GPL-3.0-only +# # Spectre & Meltdown checker # # Check for the latest version at: @@ -9,7 +11,7 @@ # # Stephane Lesimple # -VERSION='0.37' +VERSION='0.40' trap 'exit_cleanup' EXIT trap '_warn "interrupted, cleaning up..."; exit_cleanup; exit 1' INT @@ -19,6 +21,7 @@ exit_cleanup() [ -n "$dumped_config" ] && [ -f "$dumped_config" ] && rm -f "$dumped_config" [ -n "$kerneltmp" ] && [ -f "$kerneltmp" ] && rm -f "$kerneltmp" [ -n "$kerneltmp2" ] && [ -f "$kerneltmp2" ] && rm -f "$kerneltmp2" + [ -n "$mcedb_tmp" ] && [ -f "$mcedb_tmp" ] && rm -f "$mcedb_tmp" [ "$mounted_debugfs" = 1 ] && umount /sys/kernel/debug 2>/dev/null [ "$mounted_procfs" = 1 ] && umount "$procfs" 2>/dev/null [ "$insmod_cpuid" = 1 ] && rmmod cpuid 2>/dev/null @@ -26,6 +29,12 @@ exit_cleanup() [ "$kldload_cpuctl" = 1 ] && kldunload cpuctl 2>/dev/null } +# if we were git clone'd, adjust VERSION +if [ -d "$(dirname "$0")/.git" ] && which git >/dev/null 2>&1; then + describe=$(git -C "$(dirname "$0")" describe --tags --dirty 2>/dev/null) + [ -n "$describe" ] && VERSION=$(echo "$describe" | sed -e s/^v//) +fi + show_usage() { # shellcheck disable=SC2086 @@ -50,8 +59,9 @@ show_usage() Options: --no-color don't use color codes --verbose, -v increase verbosity level, possibly several times - --no-explain don't produce a human-readable explanation of actions to take to mitigate a vulnerability + --explain produce an additional human-readable explanation of actions to take to mitigate a vulnerability --paranoid require IBPB to deem Variant 2 as mitigated + also require SMT disabled + unconditional L1D flush to deem Foreshadow-NG VMM as mitigated --no-sysfs don't use the /sys interface even if present [Linux] --sysfs-only only use the /sys interface, don't run our own checks [Linux] @@ -60,14 +70,19 @@ show_usage() --arch-prefix PREFIX specify a prefix for cross-inspecting a kernel of a different arch, for example "aarch64-linux-gnu-", so that invoked tools will be prefixed with this (i.e. aarch64-linux-gnu-objdump) --batch text produce machine readable output, this is the default if --batch is specified alone + --batch short produce only one line with the vulnerabilities separated by spaces --batch json produce JSON output formatted for Puppet, Ansible, Chef... --batch nrpe produce machine readable output formatted for NRPE --batch prometheus produce output for consumption by prometheus-node-exporter - --variant [1,2,3] specify which variant you'd like to check, by default all variants are checked, + --variant [1,2,3,3a,4,l1tf] specify which variant you'd like to check, by default all variants are checked + --cve [cve1,cve2,...] specify which CVE you'd like to check, by default all supported CVEs are checked can be specified multiple times (e.g. --variant 2 --variant 3) --hw-only only check for CPU information, don't check for any variant --no-hw skip CPU information and checks, if you're inspecting a kernel not to be run on this host + --vmm [auto,yes,no] override the detection of the presence of a hypervisor (for CVE-2018-3646), default: auto + --update-mcedb update our local copy of the CPU microcodes versions database (from the awesome MCExtractor project) + --update-builtin-mcedb same as --update-mcedb but update builtin DB inside the script itself Return codes: 0 (not vulnerable), 2 (vulnerable), 3 (unknown), 255 (error) @@ -119,24 +134,25 @@ opt_live_explicit=0 opt_live=1 opt_no_color=0 opt_batch=0 -opt_batch_format="text" +opt_batch_format='text' opt_verbose=1 -opt_variant1=0 -opt_variant2=0 -opt_variant3=0 -opt_allvariants=1 +opt_cve_list='' +opt_cve_all=1 opt_no_sysfs=0 opt_sysfs_only=0 opt_coreos=0 opt_arch_prefix='' opt_hw_only=0 opt_no_hw=0 -opt_no_explain=0 +opt_vmm=-1 +opt_explain=0 opt_paranoid=0 global_critical=0 global_unknown=0 -nrpe_vuln="" +nrpe_vuln='' + +supported_cve_list='CVE-2017-5753 CVE-2017-5715 CVE-2017-5754 CVE-2018-3640 CVE-2018-3639 CVE-2018-3615 CVE-2018-3620 CVE-2018-3646' # find a sane command to print colored messages, we prefer `printf` over `echo` # because `printf` behavior is more standard across Linux/BSD @@ -148,12 +164,12 @@ if which printf >/dev/null 2>&1; then elif which echo >/dev/null 2>&1; then echo_cmd=$(which echo) else - # which command is broken? + # maybe the `which` command is broken? [ -x /bin/echo ] && echo_cmd=/bin/echo # for Android [ -x /system/bin/echo ] && echo_cmd=/system/bin/echo fi -# still empty ? fallback to builtin +# still empty? fallback to builtin [ -z "$echo_cmd" ] && echo_cmd=echo __echo() { @@ -233,32 +249,51 @@ _debug() explain() { - if [ "$opt_no_explain" != 1 ] ; then + if [ "$opt_explain" = 1 ] ; then _info '' _info "> \033[41m\033[30mHow to fix:\033[0m $*" fi } +cve2name() +{ + case "$1" in + CVE-2017-5753) echo "Spectre Variant 1, bounds check bypass";; + CVE-2017-5715) echo "Spectre Variant 2, branch target injection";; + CVE-2017-5754) echo "Variant 3, Meltdown, rogue data cache load";; + CVE-2018-3640) echo "Variant 3a, rogue system register read";; + CVE-2018-3639) echo "Variant 4, speculative store bypass";; + CVE-2018-3615) echo "Foreshadow (SGX), L1 terminal fault";; + CVE-2018-3620) echo "Foreshadow-NG (OS), L1 terminal fault";; + CVE-2018-3646) echo "Foreshadow-NG (VMM), L1 terminal fault";; + esac +} + is_cpu_vulnerable_cached=0 _is_cpu_vulnerable_cached() { # shellcheck disable=SC2086 - [ "$1" = 1 ] && return $variant1 - # shellcheck disable=SC2086 - [ "$1" = 2 ] && return $variant2 - # shellcheck disable=SC2086 - [ "$1" = 3 ] && return $variant3 + case "$1" in + CVE-2017-5753) return $variant1;; + CVE-2017-5715) return $variant2;; + CVE-2017-5754) return $variant3;; + CVE-2018-3640) return $variant3a;; + CVE-2018-3639) return $variant4;; + CVE-2018-3615) return $variantl1tf_sgx;; + CVE-2018-3620) return $variantl1tf;; + CVE-2018-3646) return $variantl1tf;; + esac echo "$0: error: invalid variant '$1' passed to is_cpu_vulnerable()" >&2 exit 255 } is_cpu_vulnerable() { - # param: 1, 2 or 3 (variant) + # param: one of the $supported_cve_list items # returns 0 if vulnerable, 1 if not vulnerable # (note that in shell, a return of 0 is success) # by default, everything is vulnerable, we work in a "whitelist" logic here. - # usage: is_cpu_vulnerable 2 && do something if vulnerable + # usage: is_cpu_vulnerable CVE-xxxx-yyyy && do something if vulnerable if [ "$is_cpu_vulnerable_cached" = 1 ]; then _is_cpu_vulnerable_cached "$1" return $? @@ -267,11 +302,17 @@ is_cpu_vulnerable() variant1='' variant2='' variant3='' + variant3a='' + variant4='' + variantl1tf='' if is_cpu_specex_free; then variant1=immune variant2=immune variant3=immune + variant3a=immune + variant4=immune + variantl1tf=immune elif is_intel; then # Intel # https://github.com/crozone/SpectrePoC/issues/1 ^F E5200 => spectre 2 not vulnerable @@ -286,15 +327,74 @@ is_cpu_vulnerable() # capability bit for future Intel processor that will explicitly state # that they're not vulnerable to Meltdown # this var is set in check_cpu() - variant3=immune - _debug "is_cpu_vulnerable: RDCL_NO is set so not vuln to meltdown" + [ -z "$variant3" ] && variant3=immune + [ -z "$variantl1tf" ] && variantl1tf=immune + _debug "is_cpu_vulnerable: RDCL_NO is set so not vuln to meltdown nor l1tf" + fi + if [ "$capabilities_ssb_no" = 1 ]; then + # capability bit for future Intel processor that will explicitly state + # that they're not vulnerable to Variant 4 + # this var is set in check_cpu() + [ -z "$variant4" ] && variant4=immune + _debug "is_cpu_vulnerable: SSB_NO is set so not vuln to variant4" + fi + if is_cpu_ssb_free; then + [ -z "$variant4" ] && variant4=immune + _debug "is_cpu_vulnerable: cpu not affected by speculative store bypass so not vuln to variant4" + fi + # variant 4a for xeon phi + if [ "$cpu_family" = 6 ]; then + if [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] || [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then + _debug "is_cpu_vulnerable: xeon phi immune to variant 3a" + [ -z "$variant3a" ] && variant3a=immune + fi + fi + # L1TF (RDCL_NO already checked above) + if [ "$cpu_family" = 6 ]; then + if [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_TABLET" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_MID" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL_MID" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_X" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT_MID" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_X" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_GOLDMONT_PLUS" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then + + _debug "is_cpu_vulnerable: intel family 6 but model known to be immune" + [ -z "$variantl1tf" ] && variantl1tf=immune + else + _debug "is_cpu_vulnerable: intel family 6 is vuln" + variantl1tf=vuln + fi + elif [ "$cpu_family" -lt 6 ]; then + _debug "is_cpu_vulnerable: intel family < 6 is immune" + [ -z "$variantl1tf" ] && variantl1tf=immune fi elif is_amd; then # AMD revised their statement about variant2 => vulnerable # https://www.amd.com/en/corporate/speculative-execution variant1=vuln variant2=vuln - [ -z "$variant3" ] && variant3=immune + [ -z "$variant3" ] && variant3=immune + # https://www.amd.com/en/corporate/security-updates + # "We have not identified any AMD x86 products susceptible to the Variant 3a vulnerability in our analysis to-date." + [ -z "$variant3a" ] && variant3a=immune + if is_cpu_ssb_free; then + [ -z "$variant4" ] && variant4=immune + _debug "is_cpu_vulnerable: cpu not affected by speculative store bypass so not vuln to variant4" + fi + variantl1tf=immune + elif [ "$cpu_vendor" = CAVIUM ]; then + variant3=immune + variant3a=immune + variantl1tf=immune elif [ "$cpu_vendor" = ARM ]; then # ARM # reference: https://developer.arm.com/support/security-update @@ -313,46 +413,88 @@ is_cpu_vulnerable() if [ -n "$cpupart" ] && [ -n "$cpuarch" ]; then # Cortex-R7 and Cortex-R8 are real-time and only used in medical devices or such # I can't find their CPU part number, but it's probably not that useful anyway - # model R7 R8 A9 A15 A17 A57 A72 A73 A75 - # part ? ? 0xc09 0xc0f 0xc0e 0xd07 0xd08 0xd09 0xd0a - # arch 7? 7? 7 7 7 8 8 8 8 + # model R7 R8 A8 A9 A12 A15 A17 A57 A72 A73 A75 A76 + # part ? ? c08 c09 c0d c0f c0e d07 d08 d09 d0a d0b? + # arch 7? 7? 7 7 7 7 7 8 8 8 8 8 # - # variant 1 & variant 2 - if [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -Eq '^0x(c09|c0f|c0e)$'; then - # armv7 vulnerable chips - _debug "checking cpu$i: this armv7 vulnerable to spectre 1 & 2" + # Whitelist identified non-vulnerable processors, use vulnerability information from + # https://developer.arm.com/support/arm-security-updates/speculative-processor-vulnerability + # + # Maintain cumulative check of vulnerabilities - + # if at least one of the cpu is vulnerable, then the system is vulnerable + if [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -q -w -e 0xc08 -e 0xc09 -e 0xc0d -e 0xc0e; then variant1=vuln variant2=vuln - elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -Eq '^0x(d07|d08|d09|d0a)$'; then - # armv8 vulnerable chips - _debug "checking cpu$i: this armv8 vulnerable to spectre 1 & 2" + [ -z "$variant3" ] && variant3=immune + [ -z "$variant3a" ] && variant3a=immune + [ -z "$variant4" ] && variant4=immune + _debug "checking cpu$i: armv7 A8/A9/A12/A17 non vulnerable to variants 3, 3a & 4" + elif [ "$cpuarch" = 7 ] && echo "$cpupart" | grep -q -w -e 0xc0f; then variant1=vuln variant2=vuln - else - _debug "checking cpu$i: this arm non vulnerable to 1 & 2" - # others are not vulnerable + [ -z "$variant3" ] && variant3=immune + variant3a=vuln + [ -z "$variant4" ] && variant4=immune + _debug "checking cpu$i: armv7 A15 non vulnerable to variants 3 & 4" + elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd07 -e 0xd08; then + variant1=vuln + variant2=vuln + [ -z "$variant3" ] && variant3=immune + variant3a=vuln + variant4=vuln + _debug "checking cpu$i: armv8 A57/A72 non vulnerable to variants 3" + elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd09; then + variant1=vuln + variant2=vuln + [ -z "$variant3" ] && variant3=immune + [ -z "$variant3a" ] && variant3a=immune + variant4=vuln + _debug "checking cpu$i: armv8 A73 non vulnerable to variants 3 & 3a" + elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd0a; then + variant1=vuln + variant2=vuln + variant3=vuln + [ -z "$variant3a" ] && variant3a=immune + variant4=vuln + _debug "checking cpu$i: armv8 A75 non vulnerable to variant 3a" + elif [ "$cpuarch" = 8 ] && echo "$cpupart" | grep -q -w -e 0xd0b; then + variant1=vuln + [ -z "$variant2" ] && variant2=immune + [ -z "$variant3" ] && variant3=immune + [ -z "$variant3a" ] && variant3a=immune + variant4=vuln + _debug "checking cpu$i: armv8 A76 non vulnerable to variant 2, 3 & 3a" + elif [ "$cpuarch" -le 7 ] || ( [ "$cpuarch" = 8 ] && [ $(( cpupart )) -lt $(( 0xd07 )) ] ) ; then [ -z "$variant1" ] && variant1=immune [ -z "$variant2" ] && variant2=immune - fi - - # for variant3, only A75 is vulnerable - if [ "$cpuarch" = 8 ] && [ "$cpupart" = 0xd0a ]; then - _debug "checking cpu$i: arm A75 vulnerable to meltdown" - variant3=vuln - else - _debug "checking cpu$i: this arm non vulnerable to meltdown" [ -z "$variant3" ] && variant3=immune + [ -z "$variant3a" ] && variant3a=immune + [ -z "$variant4" ] && variant4=immune + _debug "checking cpu$i: arm arch$cpuarch, all immune (v7 or v8 and model < 0xd07)" + else + variant1=vuln + variant2=vuln + variant3=vuln + variant3a=vuln + variant4=vuln + _debug "checking cpu$i: arm unknown arch$cpuarch part$cpupart, considering vuln" fi fi - _debug "is_cpu_vulnerable: for cpu$i and so far, we have <$variant1> <$variant2> <$variant3>" + _debug "is_cpu_vulnerable: for cpu$i and so far, we have <$variant1> <$variant2> <$variant3> <$variant3a> <$variant4>" done + variantl1tf=immune fi - _debug "is_cpu_vulnerable: temp results are <$variant1> <$variant2> <$variant3>" - # if at least one of the cpu is vulnerable, then the system is vulnerable - [ "$variant1" = "immune" ] && variant1=1 || variant1=0 - [ "$variant2" = "immune" ] && variant2=1 || variant2=0 - [ "$variant3" = "immune" ] && variant3=1 || variant3=0 - _debug "is_cpu_vulnerable: final results are <$variant1> <$variant2> <$variant3>" + _debug "is_cpu_vulnerable: temp results are <$variant1> <$variant2> <$variant3> <$variant3a> <$variant4> <$variantl1tf>" + [ "$variant1" = "immune" ] && variant1=1 || variant1=0 + [ "$variant2" = "immune" ] && variant2=1 || variant2=0 + [ "$variant3" = "immune" ] && variant3=1 || variant3=0 + [ "$variant3a" = "immune" ] && variant3a=1 || variant3a=0 + [ "$variant4" = "immune" ] && variant4=1 || variant4=0 + [ "$variantl1tf" = "immune" ] && variantl1tf=1 || variantl1tf=0 + variantl1tf_sgx="$variantl1tf" + # even if we are vulnerable to L1TF, if there's no SGX, we're safe for the original foreshadow + [ "$cpuid_sgx" = 0 ] && variantl1tf_sgx=1 + _debug "is_cpu_vulnerable: final results are <$variant1> <$variant2> <$variant3> <$variant3a> <$variant4> <$variantl1tf> <$variantl1tf_sgx>" is_cpu_vulnerable_cached=1 _is_cpu_vulnerable_cached "$1" return $? @@ -363,23 +505,24 @@ is_cpu_specex_free() # return true (0) if the CPU doesn't do speculative execution, false (1) if it does. # if it's not in the list we know, return false (1). # source: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c#n882 - # { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_CEDARVIEW, X86_FEATURE_ANY }, - # { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_CLOVERVIEW, X86_FEATURE_ANY }, - # { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_LINCROFT, X86_FEATURE_ANY }, - # { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_PENWELL, X86_FEATURE_ANY }, - # { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_PINEVIEW, X86_FEATURE_ANY }, + # { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SALTWELL, X86_FEATURE_ANY }, + # { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SALTWELL_TABLET, X86_FEATURE_ANY }, + # { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_BONNELL_MID, X86_FEATURE_ANY }, + # { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SALTWELL_MID, X86_FEATURE_ANY }, + # { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_BONNELL, X86_FEATURE_ANY }, # { X86_VENDOR_CENTAUR, 5 }, # { X86_VENDOR_INTEL, 5 }, # { X86_VENDOR_NSC, 5 }, # { X86_VENDOR_ANY, 4 }, + parse_cpu_details if is_intel; then if [ "$cpu_family" = 6 ]; then - if [ "$cpu_model" = "$INTEL_FAM6_ATOM_CEDARVIEW" ] || \ - [ "$cpu_model" = "$INTEL_FAM6_ATOM_CLOVERVIEW" ] || \ - [ "$cpu_model" = "$INTEL_FAM6_ATOM_LINCROFT" ] || \ - [ "$cpu_model" = "$INTEL_FAM6_ATOM_PENWELL" ] || \ - [ "$cpu_model" = "$INTEL_FAM6_ATOM_PINEVIEW" ]; then + if [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_TABLET" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL_MID" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_SALTWELL_MID" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_BONNELL" ]; then return 0 fi elif [ "$cpu_family" = 5 ]; then @@ -390,12 +533,121 @@ is_cpu_specex_free() return 1 } +is_cpu_ssb_free() +{ + # return true (0) if the CPU isn't affected by speculative store bypass, false (1) if it does. + # if it's not in the list we know, return false (1). + # source1: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c#n945 + # source2: https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git/tree/arch/x86/kernel/cpu/common.c + # Only list CPUs that speculate but are immune, to avoid duplication of cpus listed in is_cpu_specex_free() + #{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT }, + #{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT }, + #{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT_X }, + #{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT_MID }, + #{ X86_VENDOR_INTEL, 6, INTEL_FAM6_CORE_YONAH }, + #{ X86_VENDOR_INTEL, 6, INTEL_FAM6_XEON_PHI_KNL }, + #{ X86_VENDOR_INTEL, 6, INTEL_FAM6_XEON_PHI_KNM }, + #{ X86_VENDOR_AMD, 0x12, }, + #{ X86_VENDOR_AMD, 0x11, }, + #{ X86_VENDOR_AMD, 0x10, }, + #{ X86_VENDOR_AMD, 0xf, }, + parse_cpu_details + if is_intel; then + if [ "$cpu_family" = 6 ]; then + if [ "$cpu_model" = "$INTEL_FAM6_ATOM_AIRMONT" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_X" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_ATOM_SILVERMONT_MID" ]; then + return 0 + elif [ "$cpu_model" = "$INTEL_FAM6_CORE_YONAH" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNL" ] || \ + [ "$cpu_model" = "$INTEL_FAM6_XEON_PHI_KNM" ]; then + return 0 + fi + fi + fi + if is_amd; then + if [ "$cpu_family" = "18" ] || \ + [ "$cpu_family" = "17" ] || \ + [ "$cpu_family" = "16" ] || \ + [ "$cpu_family" = "15" ]; then + return 0 + fi + fi + [ "$cpu_family" = 4 ] && return 0 + return 1 +} + show_header() { _info "Spectre and Meltdown mitigation detection tool v$VERSION" _info } +[ -z "$HOME" ] && HOME="$(getent passwd "$(whoami)" | cut -d: -f6)" +mcedb_cache="$HOME/.mcedb" +update_mcedb() +{ + # We're using MCE.db from the excellent platomav's MCExtractor project + show_header + + if [ -r "$mcedb_cache" ]; then + previous_mcedb_revision=$(awk '/^# %%% MCEDB / { print $4 }' "$mcedb_cache") + fi + + # first download the database + mcedb_tmp="$(mktemp /tmp/mcedb-XXXXXX)" + mcedb_url='https://github.com/platomav/MCExtractor/raw/master/MCE.db' + _info_nol "Fetching MCE.db from the MCExtractor project... " + if which wget >/dev/null 2>&1; then + wget -q "$mcedb_url" -O "$mcedb_tmp"; ret=$? + elif which curl >/dev/null 2>&1; then + curl -sL "$mcedb_url" -o "$mcedb_tmp"; ret=$? + elif which fetch >/dev/null 2>&1; then + fetch -q "$mcedb_url" -o "$mcedb_tmp"; ret=$? + else + echo ERROR "please install one of \`wget\`, \`curl\` of \`fetch\` programs" + return 1 + fi + if [ "$ret" != 0 ]; then + echo ERROR "error $ret while downloading MCE.db" + return $ret + fi + echo DONE + + # now extract contents using sqlite + _info_nol "Extracting data... " + if ! which sqlite3 >/dev/null 2>&1; then + echo ERROR "please install the \`sqlite3\` program" + return 1 + fi + mcedb_revision=$(sqlite3 "$mcedb_tmp" "select revision from MCE") + mcedb_date=$(sqlite3 "$mcedb_tmp" "select strftime('%Y/%m/%d', date, 'unixepoch') from MCE") + if [ -z "$mcedb_revision" ]; then + echo ERROR "downloaded file seems invalid" + return 1 + fi + echo OK "MCExtractor database revision $mcedb_revision dated $mcedb_date" + if [ -n "$previous_mcedb_revision" ]; then + if [ "$previous_mcedb_revision" = "v$mcedb_revision" ]; then + echo "We already have this version locally, no update needed" + [ "$1" != builtin ] && return 0 + fi + fi + echo "# Spectre & Meltdown Checker" > "$mcedb_cache" + echo "# %%% MCEDB v$mcedb_revision - $mcedb_date" >> "$mcedb_cache" + sqlite3 "$mcedb_tmp" "select '# I,0x'||cpuid||',0x'||version||','||max(yyyymmdd) from Intel group by cpuid order by cpuid asc; select '# A,0x'||cpuid||',0x'||version||','||max(yyyymmdd) from AMD group by cpuid order by cpuid asc" | grep -v '^# .,0x00000000,' >> "$mcedb_cache" + echo OK "local version updated" + + if [ "$1" = builtin ]; then + newfile=$(mktemp /tmp/smc-XXXXXX) + awk '/^# %%% MCEDB / { exit }; { print }' "$0" > "$newfile" + awk '{ if (NR>1) { print } }' "$mcedb_cache" >> "$newfile" + cat "$newfile" > "$0" + rm -f "$newfile" + fi +} + parse_opt_file() { # parse_opt_file option_name option_value @@ -471,14 +723,25 @@ while [ -n "$1" ]; do opt_no_hw=1 shift elif [ "$1" = "--no-explain" ]; then - opt_no_explain=1 + # deprecated, kept for compatibility + opt_explain=0 + shift + elif [ "$1" = "--update-mcedb" ]; then + update_mcedb + exit $? + elif [ "$1" = "--update-builtin-mcedb" ]; then + update_mcedb builtin + exit $? + elif [ "$1" = "--explain" ]; then + opt_explain=1 shift elif [ "$1" = "--batch" ]; then opt_batch=1 opt_verbose=0 + opt_no_color=1 shift case "$1" in - text|nrpe|json|prometheus) opt_batch_format="$1"; shift;; + text|short|nrpe|json|prometheus) opt_batch_format="$1"; shift;; --*) ;; # allow subsequent flags '') ;; # allow nothing at all *) @@ -490,17 +753,45 @@ while [ -n "$1" ]; do elif [ "$1" = "-v" ] || [ "$1" = "--verbose" ]; then opt_verbose=$(( opt_verbose + 1 )) shift - elif [ "$1" = "--variant" ]; then + elif [ "$1" = "--cve" ]; then if [ -z "$2" ]; then - echo "$0: error: option --variant expects a parameter (1, 2 or 3)" >&2 + echo "$0: error: option --cve expects a parameter, supported CVEs are: $supported_cve_list" >&2 + exit 255 + fi + selected_cve=$(echo "$supported_cve_list" | grep -iwo "$2") + if [ -n "$selected_cve" ]; then + opt_cve_list="$opt_cve_list $selected_cve" + opt_cve_all=0 + else + echo "$0: error: unsupported CVE specified ('$2'), supported CVEs are: $supported_cve_list" >&2 + exit 255 + fi + shift 2 + elif [ "$1" = "--vmm" ]; then + if [ -z "$2" ]; then + echo "$0: error: option --vmm (auto, yes, no)" >&2 exit 255 fi case "$2" in - 1) opt_variant1=1; opt_allvariants=0;; - 2) opt_variant2=1; opt_allvariants=0;; - 3) opt_variant3=1; opt_allvariants=0;; + auto) opt_vmm=-1;; + yes) opt_vmm=1;; + no) opt_vmm=0;; + esac + shift 2 + elif [ "$1" = "--variant" ]; then + if [ -z "$2" ]; then + echo "$0: error: option --variant expects a parameter (1, 2, 3, 3a, 4 or l1tf)" >&2 + exit 255 + fi + case "$2" in + 1) opt_cve_list="$opt_cve_list CVE-2017-5753"; opt_cve_all=0;; + 2) opt_cve_list="$opt_cve_list CVE-2017-5715"; opt_cve_all=0;; + 3) opt_cve_list="$opt_cve_list CVE-2017-5754"; opt_cve_all=0;; + 3a) opt_cve_list="$opt_cve_list CVE-2018-3640"; opt_cve_all=0;; + 4) opt_cve_list="$opt_cve_list CVE-2018-3639"; opt_cve_all=0;; + l1tf) opt_cve_list="$opt_cve_list CVE-2018-3615 CVE-2018-3620 CVE-2018-3646"; opt_cve_all=0;; *) - echo "$0: error: invalid parameter '$2' for --variant, expected either 1, 2 or 3" >&2; + echo "$0: error: invalid parameter '$2' for --variant, expected either 1, 2, 3, 3a, 4 or l1tf" >&2; exit 255 ;; esac @@ -567,10 +858,14 @@ pvulnstatus() CVE-2017-5753) aka="SPECTRE VARIANT 1";; CVE-2017-5715) aka="SPECTRE VARIANT 2";; CVE-2017-5754) aka="MELTDOWN";; + CVE-2018-3640) aka="VARIANT 3A";; + CVE-2018-3639) aka="VARIANT 4";; + CVE-2018-3615/3620/3646) aka="L1TF";; esac case "$opt_batch_format" in text) _echo 0 "$1: $2 ($3)";; + short) short_output="${short_output}$1 ";; json) case "$2" in UNK) is_vuln="null";; @@ -598,9 +893,9 @@ pvulnstatus() shift 2 _info_nol "> \033[46m\033[30mSTATUS:\033[0m " case "$vulnstatus" in - UNK) pstatus yellow 'UNKNOWN' "$@";; - VULN) pstatus red 'VULNERABLE' "$@";; - OK) pstatus green 'NOT VULNERABLE' "$@";; + UNK) pstatus yellow 'UNKNOWN' "$@"; final_summary="$final_summary \033[43m\033[30m$pvulnstatus_last_cve:??\033[0m";; + VULN) pstatus red 'VULNERABLE' "$@"; final_summary="$final_summary \033[41m\033[30m$pvulnstatus_last_cve:KO\033[0m";; + OK) pstatus green 'NOT VULNERABLE' "$@"; final_summary="$final_summary \033[42m\033[30m$pvulnstatus_last_cve:OK\033[0m";; esac } @@ -732,8 +1027,12 @@ mount_debugfs() load_msr() { if [ "$os" = Linux ]; then - modprobe msr 2>/dev/null && insmod_msr=1 - _debug "attempted to load module msr, insmod_msr=$insmod_msr" + if ! grep -e msr "$procfs/modules" 2>/dev/null; then + modprobe msr 2>/dev/null && insmod_msr=1 + _debug "attempted to load module msr, insmod_msr=$insmod_msr" + else + _debug "msr module already loaded" + fi else if ! kldstat -q -m cpuctl; then kldload cpuctl 2>/dev/null && kldload_cpuctl=1 @@ -747,8 +1046,12 @@ load_msr() load_cpuid() { if [ "$os" = Linux ]; then - modprobe cpuid 2>/dev/null && insmod_cpuid=1 - _debug "attempted to load module cpuid, insmod_cpuid=$insmod_cpuid" + if ! grep -e cpuid "$procfs/modules" 2>/dev/null; then + modprobe cpuid 2>/dev/null && insmod_cpuid=1 + _debug "attempted to load module cpuid, insmod_cpuid=$insmod_cpuid" + else + _debug "cpuid module already loaded" + fi else if ! kldstat -q -m cpuctl; then kldload cpuctl 2>/dev/null && kldload_cpuctl=1 @@ -760,9 +1063,7 @@ load_cpuid() } # shellcheck disable=SC2034 -{ EAX=1; EBX=2; ECX=3; EDX=4; -} read_cpuid() { # leaf is the value of the eax register when calling the cpuid instruction: @@ -785,11 +1086,23 @@ read_cpuid() if [ -e /dev/cpu/0/cpuid ]; then # Linux + if [ ! -r /dev/cpu/0/cpuid ]; then + return 2 + fi + # on some kernel versions, /dev/cpu/0/cpuid doesn't imply that the cpuid module is loaded, in that case dd returns an error + dd if=/dev/cpu/0/cpuid bs=16 count=1 >/dev/null 2>&1 || load_cpuid # we need _leaf to be converted to decimal for dd _leaf=$(( _leaf )) - _cpuid=$(dd if=/dev/cpu/0/cpuid bs=16 skip="$_leaf" iflag=skip_bytes count=1 2>/dev/null | od -A n -t u4) + # to avoid using iflag=skip_bytes, which doesn't exist on old versions of dd, seek to the closer multiple-of-16 + _ddskip=$(( _leaf / 16 )) + _odskip=$(( _leaf - _ddskip * 16 )) + # now read the value + _cpuid=$(dd if=/dev/cpu/0/cpuid bs=16 skip=$_ddskip count=$((_odskip + 1)) 2>/dev/null | od -j $((_odskip * 16)) -A n -t u4) elif [ -e /dev/cpuctl0 ]; then # BSD + if [ ! -r /dev/cpuctl0 ]; then + return 2 + fi _cpuid=$(cpucontrol -i "$_leaf" /dev/cpuctl0 2>/dev/null | awk '{print $4,$5,$6,$7}') # cpuid level 0x1: 0x000306d4 0x00100800 0x4dfaebbf 0xbfebfbff else @@ -867,19 +1180,24 @@ parse_cpu_details() cpu_friendly_name="ARM" [ -n "$cpu_arch" ] && cpu_friendly_name="$cpu_friendly_name v$cpu_arch" [ -n "$cpu_part" ] && cpu_friendly_name="$cpu_friendly_name model $cpu_part" + + elif grep -qi 'CPU implementer[[:space:]]*:[[:space:]]*0x43' "$procfs/cpuinfo"; then + cpu_vendor='CAVIUM' fi cpu_family=$( grep '^cpu family' "$procfs/cpuinfo" | awk '{print $4}' | grep -E '^[0-9]+$' | head -1) cpu_model=$( grep '^model' "$procfs/cpuinfo" | awk '{print $3}' | grep -E '^[0-9]+$' | head -1) cpu_stepping=$(grep '^stepping' "$procfs/cpuinfo" | awk '{print $3}' | grep -E '^[0-9]+$' | head -1) - cpu_ucode=$( grep '^microcode' "$procfs/cpuinfo" | awk '{print $3}' | head -1) + cpu_ucode=$( grep '^microcode' "$procfs/cpuinfo" | awk '{print $3}' | head -1) else cpu_friendly_name=$(sysctl -n hw.model) fi # get raw cpuid, it's always useful (referenced in the Intel doc for firmware updates for example) if read_cpuid 0x1 $EAX 0 0xFFFFFFFF; then - cpuid="$read_cpuid_value" + cpu_cpuid="$read_cpuid_value" + else + cpu_cpuid=0 fi # under BSD, linprocfs often doesn't export ucode information, so fetch it ourselves the good old way @@ -899,8 +1217,11 @@ parse_cpu_details() fi fi - echo "$cpu_ucode" | grep -q ^0x && cpu_ucode_decimal=$(( cpu_ucode )) - ucode_found="model $cpu_model stepping $cpu_stepping ucode $cpu_ucode cpuid "$(printf "0x%x" "$cpuid") + # if we got no cpu_ucode (e.g. we're in a vm), fall back to 0x0 + [ -z "$cpu_ucode" ] && cpu_ucode=0x0 + + echo "$cpu_ucode" | grep -q ^0x && cpu_ucode=$(( cpu_ucode )) + ucode_found=$(printf "model 0x%x family 0x%x stepping 0x%x ucode 0x%x cpuid 0x%x" "$cpu_model" "$cpu_family" "$cpu_stepping" "$cpu_ucode" "$cpu_cpuid") # also define those that we will need in other funcs # taken from ttps://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/intel-family.h @@ -945,19 +1266,19 @@ parse_cpu_details() # /* "Small Core" Processors (Atom) */ - INTEL_FAM6_ATOM_PINEVIEW=$(( 0x1C )) - INTEL_FAM6_ATOM_LINCROFT=$(( 0x26 )) - INTEL_FAM6_ATOM_PENWELL=$(( 0x27 )) - INTEL_FAM6_ATOM_CLOVERVIEW=$(( 0x35 )) - INTEL_FAM6_ATOM_CEDARVIEW=$(( 0x36 )) - INTEL_FAM6_ATOM_SILVERMONT1=$(( 0x37 )) - INTEL_FAM6_ATOM_SILVERMONT2=$(( 0x4D )) + INTEL_FAM6_ATOM_BONNELL=$(( 0x1C )) + INTEL_FAM6_ATOM_BONNELL_MID=$(( 0x26 )) + INTEL_FAM6_ATOM_SALTWELL_MID=$(( 0x27 )) + INTEL_FAM6_ATOM_SALTWELL_TABLET=$(( 0x35 )) + INTEL_FAM6_ATOM_SALTWELL=$(( 0x36 )) + INTEL_FAM6_ATOM_SILVERMONT=$(( 0x37 )) + INTEL_FAM6_ATOM_SILVERMONT_MID=$(( 0x4A )) + INTEL_FAM6_ATOM_SILVERMONT_X=$(( 0x4D )) INTEL_FAM6_ATOM_AIRMONT=$(( 0x4C )) - INTEL_FAM6_ATOM_MERRIFIELD=$(( 0x4A )) - INTEL_FAM6_ATOM_MOOREFIELD=$(( 0x5A )) + INTEL_FAM6_ATOM_AIRMONT_MID=$(( 0x5A )) INTEL_FAM6_ATOM_GOLDMONT=$(( 0x5C )) - INTEL_FAM6_ATOM_DENVERTON=$(( 0x5F )) - INTEL_FAM6_ATOM_GEMINI_LAKE=$(( 0x7A )) + INTEL_FAM6_ATOM_GOLDMONT_X=$(( 0x5F )) + INTEL_FAM6_ATOM_GOLDMONT_PLUS=$(( 0x7A )) # /* Xeon Phi */ @@ -1035,10 +1356,9 @@ is_ucode_blacklisted() do model=$(echo $tuple | cut -d, -f1) stepping=$(( $(echo $tuple | cut -d, -f2) )) - ucode=$(echo $tuple | cut -d, -f3) - echo "$ucode" | grep -q ^0x && ucode_decimal=$(( ucode )) if [ "$cpu_model" = "$model" ] && [ "$cpu_stepping" = "$stepping" ]; then - if [ "$cpu_ucode_decimal" = "$ucode_decimal" ] || [ "$cpu_ucode" = "$ucode" ]; then + ucode=$(( $(echo $tuple | cut -d, -f3) )) + if [ "$cpu_ucode" = "$ucode" ]; then _debug "is_ucode_blacklisted: we have a match! ($cpu_model/$cpu_stepping/$cpu_ucode)" return 0 fi @@ -1053,7 +1373,7 @@ is_skylake_cpu() # is this a skylake cpu? # return 0 if yes, 1 otherwise #if (boot_cpu_data.x86_vendor == X86_VENDOR_INTEL && - # boot_cpu_data.x86 == 6) { + # boot_cpu_data.x86 == 6) { # switch (boot_cpu_data.x86_model) { # case INTEL_FAM6_SKYLAKE_MOBILE: # case INTEL_FAM6_SKYLAKE_DESKTOP: @@ -1074,6 +1394,17 @@ is_skylake_cpu() return 1 } +is_vulnerable_to_empty_rsb() +{ + if is_intel && [ -z "$capabilities_rsba" ]; then + _warn "is_vulnerable_to_empty_rsb() called before ARCH CAPABILITIES MSR was read" + fi + if is_skylake_cpu || [ "$capabilities_rsba" = 1 ]; then + return 0 + fi + return 1 +} + is_zen_cpu() { # is this CPU from the AMD ZEN family ? (ryzen, epyc, ...) @@ -1083,6 +1414,50 @@ is_zen_cpu() return 1 } +if [ -r "$mcedb_cache" ]; then + mcedb_source="$mcedb_cache" + mcedb_info="local MCExtractor DB "$(grep -E '^# %%% MCEDB ' "$mcedb_source" | cut -c13-) +else + mcedb_source="$0" + mcedb_info="builtin MCExtractor DB "$(grep -E '^# %%% MCEDB ' "$mcedb_source" | cut -c13-) +fi +read_mcedb() +{ + awk '{ if (DELIM==1) { print $2 } } /^# %%% MCEDB / { DELIM=1 }' "$mcedb_source" +} + +is_latest_known_ucode() +{ + # 0: yes, 1: no, 2: unknown + parse_cpu_details + if [ "$cpu_cpuid" = 0 ]; then + ucode_latest="couldn't get your cpuid" + return 2 + fi + ucode_latest="latest microcode version for your CPU model is unknown" + if is_intel; then + cpu_brand_prefix=I + elif is_amd; then + cpu_brand_prefix=A + else + return 2 + fi + for tuple in $(read_mcedb | grep "$(printf "^$cpu_brand_prefix,0x%08X," "$cpu_cpuid")") + do + ucode=$(( $(echo "$tuple" | cut -d, -f3) )) + ucode_date=$(echo "$tuple" | cut -d, -f4 | sed -r 's=(....)(..)(..)=\1/\2/\3=') + _debug "is_latest_known_ucode: with cpuid $cpu_cpuid has ucode $cpu_ucode, last known is $ucode from $ucode_date" + ucode_latest=$(printf "latest version is 0x%x dated $ucode_date according to $mcedb_info" "$ucode") + if [ "$cpu_ucode" -ge "$ucode" ]; then + return 0 + else + return 1 + fi + done + _debug "is_latest_known_ucode: this cpuid is not referenced ($cpu_cpuid)" + return 2 +} + # ENTRYPOINT # we can't do anything useful under WSL @@ -1102,15 +1477,13 @@ if [ "$opt_live_explicit" = 1 ]; then fi fi if [ "$opt_hw_only" = 1 ]; then - if [ "$opt_allvariants" = 0 ]; then + if [ "$opt_cve_all" = 0 ]; then show_usage echo "$0: error: incompatible modes specified, --hw-only vs --variant" >&2 exit 255 else - opt_allvariants=0 - opt_variant1=0 - opt_variant2=0 - opt_variant3=0 + opt_cve_all=0 + opt_cve_list='' fi fi @@ -1170,9 +1543,12 @@ if [ "$opt_live" = 1 ]; then # try to find the image of the current running kernel # first, look for the BOOT_IMAGE hint in the kernel cmdline - if [ -r /proc/cmdline ] && grep -q 'BOOT_IMAGE=' /proc/cmdline; then - opt_kernel=$(grep -Eo 'BOOT_IMAGE=[^ ]+' /proc/cmdline | cut -d= -f2) - _debug "found opt_kernel=$opt_kernel in /proc/cmdline" + if [ -r "$procfs/cmdline" ] && grep -q 'BOOT_IMAGE=' "$procfs/cmdline"; then + opt_kernel=$(grep -Eo 'BOOT_IMAGE=[^ ]+' "$procfs/cmdline" | cut -d= -f2) + _debug "found opt_kernel=$opt_kernel in $procfs/cmdline" + # if the boot partition is within a btrfs subvolume, strip the subvolume name + # if /boot is a separate subvolume, the remainder of the code in this section should handle it + if echo "$opt_kernel" | grep -q "^/@"; then opt_kernel=$(echo "$opt_kernel" | sed "s:/@[^/]*::"); fi # if we have a dedicated /boot partition, our bootloader might have just called it / # so try to prepend /boot and see if we find anything [ -e "/boot/$opt_kernel" ] && opt_kernel="/boot/$opt_kernel" @@ -1187,8 +1563,12 @@ if [ "$opt_live" = 1 ]; then [ -e "/lib/modules/$(uname -r)/vmlinuz" ] && opt_kernel="/lib/modules/$(uname -r)/vmlinuz" # Slackare: [ -e "/boot/vmlinuz" ] && opt_kernel="/boot/vmlinuz" - # Arch: - [ -e "/boot/vmlinuz-linux" ] && opt_kernel="/boot/vmlinuz-linux" + # Arch aarch64: + [ -e "/boot/Image" ] && opt_kernel="/boot/Image" + # Arch armv5/armv7: + [ -e "/boot/zImage" ] && opt_kernel="/boot/zImage" + # Arch arm7: + [ -e "/boot/kernel7.img" ] && opt_kernel="/boot/kernel7.img" # Linux-Libre: [ -e "/boot/vmlinuz-linux-libre" ] && opt_kernel="/boot/vmlinuz-linux-libre" # pine64 @@ -1203,27 +1583,37 @@ if [ "$opt_live" = 1 ]; then [ -e "/run/booted-system/kernel" ] && opt_kernel="/run/booted-system/kernel" # systemd kernel-install: [ -e "/etc/machine-id" ] && [ -e "/boot/$(cat /etc/machine-id)/$(uname -r)/linux" ] && opt_kernel="/boot/$(cat /etc/machine-id)/$(uname -r)/linux" + # Clear Linux: + str_uname=$(uname -r) + clear_linux_kernel="/lib/kernel/org.clearlinux.${str_uname##*.}.${str_uname%.*}" + [ -e "$clear_linux_kernel" ] && opt_kernel=$clear_linux_kernel fi # system.map - if [ -e /proc/kallsyms ] ; then - opt_map=/proc/kallsyms + if [ -e "$procfs/kallsyms" ] ; then + opt_map="$procfs/kallsyms" elif [ -e "/lib/modules/$(uname -r)/System.map" ] ; then opt_map="/lib/modules/$(uname -r)/System.map" elif [ -e "/boot/System.map-$(uname -r)" ] ; then opt_map="/boot/System.map-$(uname -r)" + elif [ -e "/lib/kernel/System.map-$(uname -r)" ]; then + opt_map="/lib/kernel/System.map-$(uname -r)" fi # config - if [ -e /proc/config.gz ] ; then + if [ -e "$procfs/config.gz" ] ; then dumped_config="$(mktemp /tmp/config-XXXXXX)" - gunzip -c /proc/config.gz > "$dumped_config" + gunzip -c "$procfs/config.gz" > "$dumped_config" # dumped_config will be deleted at the end of the script opt_config="$dumped_config" elif [ -e "/lib/modules/$(uname -r)/config" ]; then opt_config="/lib/modules/$(uname -r)/config" elif [ -e "/boot/config-$(uname -r)" ]; then opt_config="/boot/config-$(uname -r)" + elif [ -e "/etc/kernels/kernel-config-$(uname -m)-$(uname -r)" ]; then + opt_config="/etc/kernels/kernel-config-$(uname -m)-$(uname -r)" + elif [ -e "/lib/kernel/config-$(uname -r)" ]; then + opt_config="/lib/kernel/config-$(uname -r)" fi else _info "Checking for vulnerabilities against specified kernel" @@ -1245,7 +1635,7 @@ if [ "$os" = Linux ]; then fi if [ -n "$dumped_config" ] && [ -n "$opt_config" ]; then - _verbose "Will use kconfig \033[35m/proc/config.gz (decompressed)\033[0m" + _verbose "Will use kconfig \033[35m$procfs/config.gz (decompressed)\033[0m" elif [ -n "$opt_config" ]; then _verbose "Will use kconfig \033[35m$opt_config\033[0m" else @@ -1261,7 +1651,7 @@ if [ "$os" = Linux ]; then fi if [ "$bad_accuracy" = 1 ]; then - _info "We're missing some kernel info (see -v), accuracy might be reduced" + _warn "We're missing some kernel info (see -v), accuracy might be reduced" fi fi @@ -1269,7 +1659,7 @@ if [ -e "$opt_kernel" ]; then if ! which "${opt_arch_prefix}readelf" >/dev/null 2>&1; then _debug "readelf not found" kernel_err="missing '${opt_arch_prefix}readelf' tool, please install it, usually it's in the 'binutils' package" - elif [ "$opt_sysfs_only" = 1 ]; then + elif [ "$opt_sysfs_only" = 1 ] || [ "$opt_hw_only" = 1 ]; then kernel_err='kernel image decompression skipped' else extract_kernel "$opt_kernel" @@ -1316,18 +1706,26 @@ _info sys_interface_check() { - [ "$opt_live" = 1 ] && [ "$opt_no_sysfs" = 0 ] && [ -r "$1" ] || return 1 + file="$1" + regex="$2" + mode="$3" + [ "$opt_live" = 1 ] && [ "$opt_no_sysfs" = 0 ] && [ -r "$file" ] || return 1 + [ -n "$regex" ] || regex='.*' + msg=$(grep -Eo "$regex" "$file") + if [ "$mode" = silent ]; then + _info "* Information from the /sys interface: $msg" + return 0 + fi _info_nol "* Mitigated according to the /sys interface: " - msg=$(cat "$1") - if grep -qi '^not affected' "$1"; then + if echo "$msg" | grep -qi '^not affected'; then # Not affected status=OK pstatus green YES "$msg" - elif grep -qi '^mitigation' "$1"; then + elif echo "$msg" | grep -qi '^mitigation'; then # Mitigation: PTI status=OK pstatus green YES "$msg" - elif grep -qi '^vulnerable' "$1"; then + elif echo "$msg" | grep -qi '^vulnerable'; then # Vulnerable status=VULN pstatus yellow NO "$msg" @@ -1335,7 +1733,7 @@ sys_interface_check() status=UNK pstatus yellow UNKNOWN "$msg" fi - _debug "sys_interface_check: $1=$msg" + _debug "sys_interface_check: $file=$msg (re=$regex)" return 0 } @@ -1356,18 +1754,39 @@ number_of_cpus() # $2 - cpu index write_msr() { + # _msr must be in hex, in the form 0x1234: + _msr="$1" + # cpu index, starting from 0: + _cpu="$2" if [ "$os" != Linux ]; then - cpucontrol -m "$1=0" "/dev/cpuctl$2" >/dev/null 2>&1; ret=$? + cpucontrol -m "$_msr=0" "/dev/cpuctl$_cpu" >/dev/null 2>&1; ret=$? else + # for Linux # convert to decimal - _msrindex=$(( $1 )) - if [ ! -w /dev/cpu/"$2"/msr ]; then + _msr=$(( _msr )) + if [ ! -w /dev/cpu/"$_cpu"/msr ]; then ret=200 # permission error + # if wrmsr is available, use it + elif which wrmsr >/dev/null 2>&1 && [ "$SMC_NO_WRMSR" != 1 ]; then + _debug "write_msr: using wrmsr" + wrmsr $_msr 0 2>/dev/null; ret=$? + # or if we have perl, use it, any 5.x version will work + elif which perl >/dev/null 2>&1 && [ "$SMC_NO_PERL" != 1 ]; then + _debug "write_msr: using perl" + ret=1 + perl -e "open(M,'>','/dev/cpu/$_cpu/msr') and seek(M,$_msr,0) and exit(syswrite(M,pack('H16',0)))"; [ $? -eq 8 ] && ret=0 + # fallback to dd if it supports seek_bytes + elif dd if=/dev/null of=/dev/null bs=8 count=1 seek="$_msr" oflag=seek_bytes 2>/dev/null; then + _debug "write_msr: using dd" + dd if=/dev/zero of=/dev/cpu/"$_cpu"/msr bs=8 count=1 seek="$_msr" oflag=seek_bytes 2>/dev/null; ret=$? else - dd if=/dev/zero of=/dev/cpu/"$2"/msr bs=8 count=1 seek="$_msrindex" oflag=seek_bytes 2>/dev/null; ret=$? + _debug "write_msr: got no wrmsr, perl or recent enough dd!" + return 201 # missing tool error fi fi - _debug "write_msr: for cpu $2 on msr $1 ($_msrindex), ret=$ret" + # normalize ret + [ "$ret" != 0 ] && ret=1 + _debug "write_msr: for cpu $_cpu on msr $_msr, ret=$ret" return $ret } @@ -1388,12 +1807,27 @@ read_msr() _msr_l="$(( _msr_l >> 24 & 0xFF )) $(( _msr_l >> 16 & 0xFF )) $(( _msr_l >> 8 & 0xFF )) $(( _msr_l & 0xFF ))" read_msr_value="$_msr_h $_msr_l" else + # for Linux # convert to decimal _msr=$(( _msr )) if [ ! -r /dev/cpu/"$_cpu"/msr ]; then return 200 # permission error + # if rdmsr is available, use it + elif which rdmsr >/dev/null 2>&1 && [ "$SMC_NO_RDMSR" != 1 ]; then + _debug "read_msr: using rdmsr" + read_msr_value=$(rdmsr -r $_msr 2>/dev/null | od -t u8 -A n) + # or if we have perl, use it, any 5.x version will work + elif which perl >/dev/null 2>&1 && [ "$SMC_NO_PERL" != 1 ]; then + _debug "read_msr: using perl" + read_msr_value=$(perl -e "open(M,'<','/dev/cpu/$_cpu/msr') and seek(M,$_msr,0) and read(M,\$_,8) and print" | od -t u8 -A n) + # fallback to dd if it supports skip_bytes + elif dd if=/dev/null of=/dev/null bs=8 count=1 skip="$_msr" iflag=skip_bytes 2>/dev/null; then + _debug "read_msr: using dd" + read_msr_value=$(dd if=/dev/cpu/"$_cpu"/msr bs=8 count=1 skip="$_msr" iflag=skip_bytes 2>/dev/null | od -t u8 -A n) + else + _debug "read_msr: got no rdmsr, perl or recent enough dd!" + return 201 # missing tool error fi - read_msr_value=$(dd if=/dev/cpu/"$_cpu"/msr bs=8 count=1 skip="$_msr" iflag=skip_bytes 2>/dev/null | od -t u1 -A n) if [ -z "$read_msr_value" ]; then # MSR doesn't exist, don't check for $? because some versions of dd still return 0! return 1 @@ -1403,7 +1837,6 @@ read_msr() return 0 } - check_cpu() { _info "\033[1;34mHardware check\033[0m" @@ -1427,9 +1860,7 @@ check_cpu() pstatus yellow UNKNOWN "is msr kernel module available?" else # the new MSR 'SPEC_CTRL' is at offset 0x48 - # here we use dd, it's the same as using 'rdmsr 0x48' but without needing the rdmsr tool - # if we get a read error, the MSR is not there. bs has to be 8 for msr - # skip=9 because 8*9=72=0x48 + # we check if we have it for all cpus val=0 cpu_mismatch=0 for i in $(seq 0 "$idx_max_cpu") @@ -1456,6 +1887,9 @@ check_cpu() elif [ $val -eq 200 ]; then pstatus yellow UNKNOWN "is msr kernel module available?" spec_ctrl_msr=-1 + elif [ $val -eq 201 ]; then + pstatus yellow UNKNOWN "missing tool, install either msr-tools or perl" + spec_ctrl_msr=-1 else spec_ctrl_msr=0 pstatus yellow NO @@ -1515,10 +1949,11 @@ check_cpu() _info_nol " * PRED_CMD MSR is available: " if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then pstatus yellow UNKNOWN "is msr kernel module available?" + elif [ ! -r /dev/cpu/0/msr ] && [ ! -w /dev/cpuctl0 ]; then + pstatus yellow UNKNOWN "are you root?" else # the new MSR 'PRED_CTRL' is at offset 0x49, write-only - # here we use dd, it's the same as using 'wrmsr 0x49 0' but without needing the wrmsr tool - # if we get a write error, the MSR is not there + # we test if of all cpus val=0 cpu_mismatch=0 for i in $(seq 0 "$idx_max_cpu") @@ -1619,6 +2054,95 @@ check_cpu() fi fi + # variant 4 + if is_intel; then + _info " * Speculative Store Bypass Disable (SSBD)" + _info_nol " * CPU indicates SSBD capability: " + read_cpuid 0x7 $EDX 31 1 1; ret24=$?; ret25=$ret24 + if [ $ret24 -eq 0 ]; then + cpuid_ssbd='Intel SSBD' + fi + elif is_amd; then + _info " * Speculative Store Bypass Disable (SSBD)" + _info_nol " * CPU indicates SSBD capability: " + read_cpuid 0x80000008 $EBX 24 1 1; ret24=$? + read_cpuid 0x80000008 $EBX 25 1 1; ret25=$? + if [ $ret24 -eq 0 ]; then + cpuid_ssbd='AMD SSBD in SPEC_CTRL' + #cpuid_ssbd_spec_ctrl=1 + elif [ $ret25 -eq 0 ]; then + cpuid_ssbd='AMD SSBD in VIRT_SPEC_CTRL' + #cpuid_ssbd_virt_spec_ctrl=1 + elif [ "$cpu_family" -ge 21 ] && [ "$cpu_family" -le 23 ]; then + cpuid_ssbd='AMD non-architectural MSR' + fi + fi + + if [ -n "$cpuid_ssbd" ]; then + pstatus green YES "$cpuid_ssbd" + elif [ "$ret24" = 2 ] && [ "$ret25" = 2 ]; then + pstatus yellow UNKNOWN "is cpuid kernel module available?" + else + pstatus yellow NO + fi + + if is_amd; then + # similar to SSB_NO for intel + read_cpuid 0x80000008 $EBX 26 1 1; ret=$? + if [ $ret -eq 0 ]; then + amd_ssb_no=1 + fi + fi + + _info " * L1 data cache invalidation" + _info_nol " * FLUSH_CMD MSR is available: " + if [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then + pstatus yellow UNKNOWN "is msr kernel module available?" + elif [ ! -r /dev/cpu/0/msr ] && [ ! -w /dev/cpuctl0 ]; then + pstatus yellow UNKNOWN "are you root?" + else + # the new MSR 'FLUSH_CMD' is at offset 0x10b, write-only + # we test if of all cpus + val=0 + cpu_mismatch=0 + for i in $(seq 0 "$idx_max_cpu") + do + write_msr 0x10b "$i"; ret=$? + if [ "$i" -eq 0 ]; then + val=$ret + else + if [ "$ret" -eq $val ]; then + continue + else + cpu_mismatch=1 + fi + fi + done + + if [ $val -eq 0 ]; then + if [ $cpu_mismatch -eq 0 ]; then + pstatus green YES + cpu_flush_cmd=1 + else + pstatus green YES "But not in all CPUs" + fi + elif [ $val -eq 200 ]; then + pstatus yellow UNKNOWN "is msr kernel module available?" + else + pstatus yellow NO + fi + fi + # CPUID of L1D + _info_nol " * CPU indicates L1D flush capability: " + read_cpuid 0x7 $EDX 28 1 1; ret=$? + if [ $ret -eq 0 ]; then + pstatus green YES "L1D flush feature bit" + elif [ $ret -eq 1 ]; then + pstatus yellow NO + elif [ $ret -eq 2 ]; then + pstatus yellow UNKNOWN "is cpuid kernel module available?" + fi + if is_intel; then _info " * Enhanced IBRS (IBRS_ALL)" _info_nol " * CPU indicates ARCH_CAPABILITIES MSR availability: " @@ -1638,26 +2162,31 @@ check_cpu() _info_nol " * ARCH_CAPABILITIES MSR advertises IBRS_ALL capability: " capabilities_rdcl_no=-1 capabilities_ibrs_all=-1 + capabilities_rsba=-1 + capabilities_l1dflush_no=-1 + capabilities_ssb_no=-1 if [ "$cpuid_arch_capabilities" = -1 ]; then pstatus yellow UNKNOWN elif [ "$cpuid_arch_capabilities" != 1 ]; then capabilities_rdcl_no=0 capabilities_ibrs_all=0 + capabilities_rsba=0 + capabilities_l1dflush_no=0 + capabilities_ssb_no=0 pstatus yellow NO elif [ ! -e /dev/cpu/0/msr ] && [ ! -e /dev/cpuctl0 ]; then spec_ctrl_msr=-1 pstatus yellow UNKNOWN "is msr kernel module available?" else # the new MSR 'ARCH_CAPABILITIES' is at offset 0x10a - # here we use dd, it's the same as using 'rdmsr 0x10a' but without needing the rdmsr tool - # if we get a read error, the MSR is not there. bs has to be 8 for msr + # we check if we have it for all cpus val=0 val_cap_msr=0 cpu_mismatch=0 for i in $(seq 0 "$idx_max_cpu") do read_msr 0x10a "$i"; ret=$? - capabilities=$(echo "$read_msr_value" | awk '{print $8}') + capabilities=$read_msr_value if [ "$i" -eq 0 ]; then val=$ret val_cap_msr=$capabilities @@ -1672,15 +2201,21 @@ check_cpu() capabilities=$val_cap_msr capabilities_rdcl_no=0 capabilities_ibrs_all=0 + capabilities_rsba=0 + capabilities_l1dflush_no=0 + capabilities_ssb_no=0 if [ $val -eq 0 ]; then - _debug "capabilities MSR lower byte is $capabilities (decimal)" - [ $(( capabilities & 1 )) -eq 1 ] && capabilities_rdcl_no=1 - [ $(( capabilities & 2 )) -eq 2 ] && capabilities_ibrs_all=1 - _debug "capabilities says rdcl_no=$capabilities_rdcl_no ibrs_all=$capabilities_ibrs_all" + _debug "capabilities MSR is $capabilities (decimal)" + [ $(( capabilities >> 0 & 1 )) -eq 1 ] && capabilities_rdcl_no=1 + [ $(( capabilities >> 1 & 1 )) -eq 1 ] && capabilities_ibrs_all=1 + [ $(( capabilities >> 2 & 1 )) -eq 1 ] && capabilities_rsba=1 + [ $(( capabilities >> 3 & 1 )) -eq 1 ] && capabilities_l1dflush_no=1 + [ $(( capabilities >> 4 & 1 )) -eq 1 ] && capabilities_ssb_no=1 + _debug "capabilities says rdcl_no=$capabilities_rdcl_no ibrs_all=$capabilities_ibrs_all rsba=$capabilities_rsba l1dflush_no=$capabilities_l1dflush_no ssb_no=$capabilities_ssb_no" if [ "$capabilities_ibrs_all" = 1 ]; then if [ $cpu_mismatch -eq 0 ]; then pstatus green YES - else: + else pstatus green YES "But not in all CPUs" fi else @@ -1688,6 +2223,8 @@ check_cpu() fi elif [ $val -eq 200 ]; then pstatus yellow UNKNOWN "is msr kernel module available?" + elif [ $val -eq 201 ]; then + pstatus yellow UNKNOWN "missing tool, install either msr-tools or perl" else pstatus yellow NO fi @@ -1701,6 +2238,49 @@ check_cpu() else pstatus yellow NO fi + + _info_nol " * CPU explicitly indicates not being vulnerable to Variant 4 (SSB_NO): " + if [ "$capabilities_ssb_no" = -1 ]; then + pstatus yellow UNKNOWN + elif [ "$capabilities_ssb_no" = 1 ] || [ "$amd_ssb_no" = 1 ]; then + pstatus green YES + else + pstatus yellow NO + fi + + _info_nol " * CPU/Hypervisor indicates L1D flushing is not necessary on this system: " + if [ "$capabilities_l1dflush_no" = -1 ]; then + pstatus yellow UNKNOWN + elif [ "$capabilities_l1dflush_no" = 1 ]; then + pstatus green YES + else + pstatus yellow NO + fi + + _info_nol " * Hypervisor indicates host CPU might be vulnerable to RSB underflow (RSBA): " + if [ "$capabilities_rsba" = -1 ]; then + pstatus yellow UNKNOWN + elif [ "$capabilities_rsba" = 1 ]; then + pstatus yellow YES + else + pstatus blue NO + fi + fi + + _info_nol " * CPU supports Software Guard Extensions (SGX): " + ret=1 + cpuid_sgx=0 + if is_intel; then + read_cpuid 0x7 $EBX 2 1 1; ret=$? + fi + if [ $ret -eq 0 ]; then + pstatus blue YES + cpuid_sgx=1 + elif [ $ret -eq 2 ]; then + pstatus yellow UNKNOWN "is cpuid kernel module available?" + cpuid_sgx=-1 + else + pstatus green NO fi _info_nol " * CPU microcode is known to cause stability problems: " @@ -1715,14 +2295,24 @@ check_cpu() else pstatus blue NO "$ucode_found" fi + + _info_nol " * CPU microcode is the latest known available version: " + is_latest_known_ucode; ret=$? + if [ $ret -eq 0 ]; then + pstatus green YES "$ucode_latest" + elif [ $ret -eq 1 ]; then + pstatus red NO "$ucode_latest" + else + pstatus blue UNKNOWN "$ucode_latest" + fi } check_cpu_vulnerabilities() { - _info "* CPU vulnerability to the three speculative execution attack variants" - for v in 1 2 3; do - _info_nol " * Vulnerable to Variant $v: " - if is_cpu_vulnerable $v; then + _info "* CPU vulnerability to the speculative execution attack variants" + for cve in $supported_cve_list; do + _info_nol " * Vulnerable to $cve ($(cve2name "$cve")): " + if is_cpu_vulnerable "$cve"; then pstatus yellow YES else pstatus green NO @@ -1758,22 +2348,24 @@ check_redhat_canonical_spectre() fi } - ################### -# SPECTRE VARIANT 1 -check_variant1() +# SPECTRE 1 SECTION + +# bounds check bypass aka 'Spectre Variant 1' +check_CVE_2017_5753() { - _info "\033[1;34mCVE-2017-5753 [bounds check bypass] aka 'Spectre Variant 1'\033[0m" + cve='CVE-2017-5753' + _info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m" if [ "$os" = Linux ]; then - check_variant1_linux + check_CVE_2017_5753_linux elif echo "$os" | grep -q BSD; then - check_variant1_bsd + check_CVE_2017_5753_bsd else _warn "Unsupported OS ($os)" fi } -check_variant1_linux() +check_CVE_2017_5753_linux() { status=UNK sys_interface_available=0 @@ -1787,7 +2379,7 @@ check_variant1_linux() fi if [ "$opt_sysfs_only" != 1 ]; then # no /sys interface (or offline mode), fallback to our own ways - _info_nol "* Kernel has array_index_mask_nospec (x86): " + _info_nol "* Kernel has array_index_mask_nospec: " # vanilla: look for the Linus' mask aka array_index_mask_nospec() # that is inlined at least in raw_copy_from_user (__get_user_X symbols) #mov PER_CPU_VAR(current_task), %_ASM_DX @@ -1799,6 +2391,22 @@ check_variant1_linux() #ASM_STAC # x86 64bits: jae(0x0f 0x83 0x?? 0x?? 0x?? 0x??) sbb(0x48 0x19 0xd2) and(0x48 0x21 0xd0) # x86 32bits: cmp(0x3b 0x82 0x?? 0x?? 0x00 0x00) jae(0x73 0x??) sbb(0x19 0xd2) and(0x21 0xd0) + # + # arm32 + ##ifdef CONFIG_THUMB2_KERNEL + ##define CSDB ".inst.w 0xf3af8014" + ##else + ##define CSDB ".inst 0xe320f014" e320f014 + ##endif + #asm volatile( + # "cmp %1, %2\n" e1500003 + #" sbc %0, %1, %1\n" e0c03000 + #CSDB + #: "=r" (mask) + #: "r" (idx), "Ir" (sz) + #: "cc"); + # + # http://git.arm.linux.org.uk/cgit/linux-arm.git/commit/?h=spectre&id=a78d156587931a2c3b354534aa772febf6c9e855 if [ -n "$kernel_err" ]; then pstatus yellow UNKNOWN "couldn't check ($kernel_err)" elif ! which perl >/dev/null 2>&1; then @@ -1806,15 +2414,21 @@ check_variant1_linux() else perl -ne '/\x0f\x83....\x48\x19\xd2\x48\x21\xd0/ and $found++; END { exit($found) }' "$kernel"; ret=$? if [ $ret -gt 0 ]; then - pstatus green YES "$ret occurrence(s) found of 64 bits array_index_mask_nospec()" - v1_mask_nospec="64 bits array_index_mask_nospec" + pstatus green YES "$ret occurrence(s) found of x86 64 bits array_index_mask_nospec()" + v1_mask_nospec="x86 64 bits array_index_mask_nospec" else perl -ne '/\x3b\x82..\x00\x00\x73.\x19\xd2\x21\xd0/ and $found++; END { exit($found) }' "$kernel"; ret=$? if [ $ret -gt 0 ]; then - pstatus green YES "$ret occurrence(s) found of 32 bits array_index_mask_nospec()" - v1_mask_nospec="32 bits array_index_mask_nospec" + pstatus green YES "$ret occurrence(s) found of x86 32 bits array_index_mask_nospec()" + v1_mask_nospec="x86 32 bits array_index_mask_nospec" else - pstatus yellow NO + ret=$("${opt_arch_prefix}objdump" -d "$kernel" | grep -w -e f3af8014 -e e320f014 -B2 | grep -B1 -w sbc | grep -w -c cmp) + if [ "$ret" -gt 0 ]; then + pstatus green YES "$ret occurrence(s) found of arm 32 bits array_index_mask_nospec()" + v1_mask_nospec="arm 32 bits array_index_mask_nospec" + else + pstatus yellow NO + fi fi fi fi @@ -1833,7 +2447,7 @@ check_variant1_linux() pstatus yellow NO fi - _info_nol "* Kernel has mask_nospec64 (arm): " + _info_nol "* Kernel has mask_nospec64 (arm64): " #.macro mask_nospec64, idx, limit, tmp #sub \tmp, \idx, \limit #bic \tmp, \tmp, \idx @@ -1860,13 +2474,12 @@ check_variant1_linux() "${opt_arch_prefix}objdump" -d "$kernel" | perl -ne 'push @r, $_; /\s(hint|csdb)\s/ && $r[0]=~/\ssub\s+(x\d+)/ && $r[1]=~/\sbic\s+$1,\s+$1,/ && $r[2]=~/\sand\s/ && exit(9); shift @r if @r>3'; ret=$? if [ "$ret" -eq 9 ]; then pstatus green YES "mask_nospec64 macro is present and used" - v1_mask_nospec="arm mask_nospec64" + v1_mask_nospec="arm64 mask_nospec64" else pstatus yellow NO fi fi - if [ "$opt_verbose" -ge 2 ] || ( [ -z "$v1_mask_nospec" ] && [ "$redhat_canonical_spectre" != 1 ] && [ "$redhat_canonical_spectre" != 2 ] ); then # this is a slow heuristic and we don't need it if we already know the kernel is patched # but still show it in verbose mode @@ -1902,9 +2515,7 @@ check_variant1_linux() fi # report status - cve='CVE-2017-5753' - - if ! is_cpu_vulnerable 1; then + if ! is_cpu_vulnerable "$cve"; then # override status & msg in case CPU is not vulnerable after all pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" elif [ -z "$msg" ]; then @@ -1937,10 +2548,9 @@ check_variant1_linux() fi } -check_variant1_bsd() +check_CVE_2017_5753_bsd() { - cve='CVE-2017-5753' - if ! is_cpu_vulnerable 1; then + if ! is_cpu_vulnerable "$cve"; then # override status & msg in case CPU is not vulnerable after all pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" else @@ -1948,22 +2558,24 @@ check_variant1_bsd() fi } - ################### -# SPECTRE VARIANT 2 -check_variant2() +# SPECTRE 2 SECTION + +# branch target injection aka 'Spectre Variant 2' +check_CVE_2017_5715() { - _info "\033[1;34mCVE-2017-5715 [branch target injection] aka 'Spectre Variant 2'\033[0m" + cve='CVE-2017-5715' + _info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m" if [ "$os" = Linux ]; then - check_variant2_linux + check_CVE_2017_5715_linux elif echo "$os" | grep -q BSD; then - check_variant2_bsd + check_CVE_2017_5715_bsd else _warn "Unsupported OS ($os)" fi } -check_variant2_linux() +check_CVE_2017_5715_linux() { status=UNK sys_interface_available=0 @@ -1990,7 +2602,7 @@ check_variant2_linux() for dir in \ /sys/kernel/debug \ /sys/kernel/debug/x86 \ - /proc/sys/kernel; do + "$procfs/sys/kernel"; do if [ -e "$dir/ibrs_enabled" ]; then # if the file is there, we have IBRS compiled-in # /sys/kernel/debug/ibrs_enabled: vanilla @@ -2029,7 +2641,7 @@ check_variant2_linux() fi if [ -e "/sys/devices/system/cpu/vulnerabilities/spectre_v2" ]; then # when IBPB is enabled on 4.15+, we can see it in sysfs - if grep -q ', IBPB' "/sys/devices/system/cpu/vulnerabilities/spectre_v2"; then + if grep -q 'IBPB' "/sys/devices/system/cpu/vulnerabilities/spectre_v2"; then _debug "ibpb: found enabled in sysfs" [ -z "$ibpb_supported" ] && ibpb_supported='IBPB found enabled in sysfs' [ -z "$ibpb_enabled" ] && ibpb_enabled=1 @@ -2041,7 +2653,7 @@ check_variant2_linux() ibrs_fw_enabled=1 fi # when IBRS is enabled on 4.15+, we can see it in sysfs - if grep -q 'Indirect Branch Restricted Speculation' "/sys/devices/system/cpu/vulnerabilities/spectre_v2"; then + if grep -q -e 'IBRS' -e 'Indirect Branch Restricted Speculation' "/sys/devices/system/cpu/vulnerabilities/spectre_v2"; then _debug "ibrs: found IBRS in sysfs" [ -z "$ibrs_supported" ] && ibrs_supported='found IBRS in sysfs' [ -z "$ibrs_enabled" ] && ibrs_enabled=3 @@ -2129,7 +2741,10 @@ check_variant2_linux() 1) if [ "$ibrs_fw_enabled" = 1 ]; then pstatus green YES "for kernel space and firmware code"; else pstatus green YES "for kernel space"; fi;; 2) if [ "$ibrs_fw_enabled" = 1 ]; then pstatus green YES "for kernel, user space, and firmware code" ; else pstatus green YES "for both kernel and user space"; fi;; 3) if [ "$ibrs_fw_enabled" = 1 ]; then pstatus green YES "for kernel and firmware code"; else pstatus green YES; fi;; - *) pstatus yellow UNKNOWN;; + *) if [ "$cpuid_ibrs" != 'SPEC_CTRL' ] && [ "$cpuid_ibrs" != 'IBRS_SUPPORT' ] && [ "$cpuid_spec_ctrl" != -1 ]; + then pstatus yellow NO; _debug "ibrs: known cpu not supporting SPEC-CTRL or IBRS"; + else + pstatus yellow UNKNOWN; fi;; esac fi else @@ -2300,7 +2915,7 @@ check_variant2_linux() fi fi - if is_skylake_cpu || [ "$opt_verbose" -ge 2 ]; then + if is_vulnerable_to_empty_rsb || [ "$opt_verbose" -ge 2 ]; then _info_nol " * Kernel supports RSB filling: " if ! which "${opt_arch_prefix}strings" >/dev/null 2>&1; then pstatus yellow UNKNOWN "missing '${opt_arch_prefix}strings' tool, please install it, usually it's in the binutils package" @@ -2322,14 +2937,13 @@ check_variant2_linux() status=UNK fi - cve='CVE-2017-5715' - if ! is_cpu_vulnerable 2; then + if ! is_cpu_vulnerable "$cve"; then # override status & msg in case CPU is not vulnerable after all pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" else - if [ "$retpoline" = 1 ] && [ "$retpoline_compiler" = 1 ] && [ "$retp_enabled" != 0 ] && [ -n "$ibpb_enabled" ] && [ "$ibpb_enabled" -ge 1 ] && ( ! is_skylake_cpu || [ -n "$rsb_filling" ] ); then + if [ "$retpoline" = 1 ] && [ "$retpoline_compiler" = 1 ] && [ "$retp_enabled" != 0 ] && [ -n "$ibpb_enabled" ] && [ "$ibpb_enabled" -ge 1 ] && ( ! is_vulnerable_to_empty_rsb || [ -n "$rsb_filling" ] ); then pvulnstatus $cve OK "Full retpoline + IBPB are mitigating the vulnerability" - elif [ "$retpoline" = 1 ] && [ "$retpoline_compiler" = 1 ] && [ "$retp_enabled" != 0 ] && [ "$opt_paranoid" = 0 ] && ( ! is_skylake_cpu || [ -n "$rsb_filling" ] ); then + elif [ "$retpoline" = 1 ] && [ "$retpoline_compiler" = 1 ] && [ "$retp_enabled" != 0 ] && [ "$opt_paranoid" = 0 ] && ( ! is_vulnerable_to_empty_rsb || [ -n "$rsb_filling" ] ); then pvulnstatus $cve OK "Full retpoline is mitigating the vulnerability" if [ -n "$cpuid_ibpb" ]; then _warn "You should enable IBPB to complete retpoline as a Variant 2 mitigation" @@ -2359,8 +2973,8 @@ check_variant2_linux() # if we arrive here and didn't already call pvulnstatus, then it's VULN, let's explain why if [ "$pvulnstatus_last_cve" != "$cve" ]; then # explain what's needed for this CPU - if is_skylake_cpu; then - pvulnstatus $cve VULN "IBRS+IBPB or retpoline+IBPB+RBS filling, is needed to mitigate the vulnerability" + if is_vulnerable_to_empty_rsb; then + pvulnstatus $cve VULN "IBRS+IBPB or retpoline+IBPB+RSB filling, is needed to mitigate the vulnerability" explain "To mitigate this vulnerability, you need either IBRS + IBPB, both requiring hardware support from your CPU microcode in addition to kernel support, or a kernel compiled with retpoline and IBPB, with retpoline requiring a retpoline-aware compiler (re-run this script with -v to know if your version of gcc is retpoline-aware) and IBPB requiring hardware support from your CPU microcode. You also need a recent-enough kernel that supports RSB filling if you plan to use retpoline. For Skylake+ CPUs, the IBRS + IBPB approach is generally preferred as it guarantees complete protection, and the performance impact is not as high as with older CPUs in comparison with retpoline. More information about how to enable the missing bits for those two possible mitigations on your system follow. You only need to take one of the two approaches." elif is_zen_cpu; then pvulnstatus $cve VULN "retpoline+IBPB is needed to mitigate the vulnerability" @@ -2381,7 +2995,7 @@ check_variant2_linux() # if we are in live mode, we can check for a lot more stuff and explain further if [ "$opt_live" = 1 ] && [ "$vulnstatus" != "OK" ]; then - _explain_hypervisor="An updated CPU microcode will have IBRS/IBPB capabilities indicated in the Hardware Check section above. If you're running under an hypervisor (KVM, Xen, VirtualBox, VMware, ...), the hypervisor needs to be up to date to be able to export the new host CPU flags to the guest. You can run this script on the host to check if the host CPU is IBRS/IBPB. If it is, and it doesn't show up in the guest, upgrade the hypervisor." + _explain_hypervisor="An updated CPU microcode will have IBRS/IBPB capabilities indicated in the Hardware Check section above. If you're running under a hypervisor (KVM, Xen, VirtualBox, VMware, ...), the hypervisor needs to be up to date to be able to export the new host CPU flags to the guest. You can run this script on the host to check if the host CPU is IBRS/IBPB. If it is, and it doesn't show up in the guest, upgrade the hypervisor. You may need to reconfigure your VM to use a CPU model that has IBRS capability; in Libvirt, such CPUs are listed with an IBRS suffix." # IBPB (amd & intel) if ( [ -z "$ibpb_enabled" ] || [ "$ibpb_enabled" = 0 ] ) && ( is_intel || is_amd ); then if [ -z "$cpuid_ibpb" ]; then @@ -2460,7 +3074,7 @@ check_variant2_linux() # "Mitigation: IBP disabled", } -check_variant2_bsd() +check_CVE_2017_5715_bsd() { _info "* Mitigation 1" _info_nol " * Kernel supports IBRS: " @@ -2497,8 +3111,7 @@ check_variant2_bsd() fi fi - cve='CVE-2017-5715' - if ! is_cpu_vulnerable 2; then + if ! is_cpu_vulnerable "$cve"; then # override status & msg in case CPU is not vulnerable after all pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" elif [ "$retpoline" = 1 ]; then @@ -2517,8 +3130,8 @@ check_variant2_bsd() fi } -######################## -# MELTDOWN aka VARIANT 3 +################## +# MELTDOWN SECTION # no security impact but give a hint to the user in verbose mode # about PCID/INVPCID cpuid features that must be present to avoid @@ -2552,19 +3165,21 @@ pti_performance_check() fi } -check_variant3() +# rogue data cache load aka 'Meltdown' aka 'Variant 3' +check_CVE_2017_5754() { - _info "\033[1;34mCVE-2017-5754 [rogue data cache load] aka 'Meltdown' aka 'Variant 3'\033[0m" + cve='CVE-2017-5754' + _info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m" if [ "$os" = Linux ]; then - check_variant3_linux + check_CVE_2017_5754_linux elif echo "$os" | grep -q BSD; then - check_variant3_bsd + check_CVE_2017_5754_bsd else _warn "Unsupported OS ($os)" fi } -check_variant3_linux() +check_CVE_2017_5754_linux() { status=UNK sys_interface_available=0 @@ -2627,6 +3242,8 @@ check_variant3_linux() dmesg_grep="Kernel/User page tables isolation: enabled" dmesg_grep="$dmesg_grep|Kernel page table isolation enabled" dmesg_grep="$dmesg_grep|x86/pti: Unmapping kernel while in userspace" + # aarch64 + dmesg_grep="$dmesg_grep|CPU features: detected( feature)?: Kernel page table isolation \(KPTI\)" if grep ^flags "$procfs/cpuinfo" | grep -qw pti; then # vanilla PTI patch sets the 'pti' flag in cpuinfo _debug "kpti_enabled: found 'pti' flag in $procfs/cpuinfo" @@ -2675,13 +3292,13 @@ check_variant3_linux() # Test if the current host is a Xen PV Dom0 / DomU - if [ -d "/proc/xen" ]; then + if [ -d "$procfs/xen" ]; then # XXX do we have a better way that relying on dmesg? dmesg_grep 'Booting paravirtualized kernel on Xen$'; ret=$? if [ $ret -eq 2 ]; then _warn "dmesg truncated, Xen detection will be unreliable. Please reboot and relaunch this script" elif [ $ret -eq 0 ]; then - if [ -e /proc/xen/capabilities ] && grep -q "control_d" /proc/xen/capabilities; then + if [ -e "$procfs/xen/capabilities" ] && grep -q "control_d" "$procfs/xen/capabilities"; then xen_pv_domo=1 else xen_pv_domu=1 @@ -2705,8 +3322,7 @@ check_variant3_linux() fi fi - cve='CVE-2017-5754' - if ! is_cpu_vulnerable 3; then + if ! is_cpu_vulnerable "$cve"; then # override status & msg in case CPU is not vulnerable after all pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" elif [ -z "$msg" ]; then @@ -2726,7 +3342,7 @@ check_variant3_linux() if [ -n "$kpti_support" ]; then if [ -e "/sys/kernel/debug/x86/pti_enabled" ]; then explain "Your kernel supports PTI but it's disabled, you can enable it with \`echo 1 > /sys/kernel/debug/x86/pti_enabled\`" - elif grep -q -w nopti -w pti=off /proc/cmdline; then + elif grep -q -w -e nopti -e pti=off "$procfs/cmdline"; then explain "Your kernel supports PTI but it has been disabled on command-line, remove the nopti or pti=off option from your bootloader configuration" else explain "Your kernel supports PTI but it has been disabled, check \`dmesg\` right after boot to find clues why the system disabled it" @@ -2774,7 +3390,7 @@ check_variant3_linux() fi } -check_variant3_bsd() +check_CVE_2017_5754_bsd() { _info_nol "* Kernel supports Page Table Isolation (PTI): " kpti_enabled=$(sysctl -n vm.pmap.pti 2>/dev/null) @@ -2793,8 +3409,7 @@ check_variant3_bsd() pti_performance_check - cve='CVE-2017-5754' - if ! is_cpu_vulnerable 3; then + if ! is_cpu_vulnerable "$cve"; then # override status & msg in case CPU is not vulnerable after all pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" elif [ "$kpti_enabled" = 1 ]; then @@ -2806,6 +3421,541 @@ check_variant3_bsd() fi } +#################### +# VARIANT 3A SECTION + +# rogue system register read aka 'Variant 3a' +check_CVE_2018_3640() +{ + cve='CVE-2018-3640' + _info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m" + + status=UNK + sys_interface_available=0 + msg='' + + _info_nol "* CPU microcode mitigates the vulnerability: " + if [ -n "$cpuid_ssbd" ]; then + # microcodes that ship with SSBD are known to also fix variant3a + # there is no specific cpuid bit as far as we know + pstatus green YES + else + pstatus yellow NO + fi + + if ! is_cpu_vulnerable "$cve"; then + # override status & msg in case CPU is not vulnerable after all + pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" + elif [ -n "$cpuid_ssbd" ]; then + pvulnstatus $cve OK "your CPU microcode mitigates the vulnerability" + else + pvulnstatus $cve VULN "an up-to-date CPU microcode is needed to mitigate this vulnerability" + explain "The microcode of your CPU needs to be upgraded to mitigate this vulnerability. This is usually done at boot time by your kernel (the upgrade is not persistent across reboots which is why it's done at each boot). If you're using a distro, make sure you are up to date, as microcode updates are usually shipped alongside with the distro kernel. Availability of a microcode update for you CPU model depends on your CPU vendor. You can usually find out online if a microcode update is available for your CPU by searching for your CPUID (indicated in the Hardware Check section). The microcode update is enough, there is no additional OS, kernel or software change needed." + fi +} + +################### +# VARIANT 4 SECTION + +# speculative store bypass aka 'Variant 4' +check_CVE_2018_3639() +{ + cve='CVE-2018-3639' + _info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m" + if [ "$os" = Linux ]; then + check_CVE_2018_3639_linux + elif echo "$os" | grep -q BSD; then + check_CVE_2018_3639_bsd + else + _warn "Unsupported OS ($os)" + fi +} + +check_CVE_2018_3639_linux() +{ + status=UNK + sys_interface_available=0 + msg='' + if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/spec_store_bypass"; then + # this kernel has the /sys interface, trust it over everything + sys_interface_available=1 + fi + if [ "$opt_sysfs_only" != 1 ]; then + _info_nol "* Kernel supports speculation store bypass: " + if [ "$opt_live" = 1 ]; then + if grep -Eq 'Speculation.?Store.?Bypass:' "$procfs/self/status" 2>/dev/null; then + kernel_ssb="found in $procfs/self/status" + _debug "found Speculation.Store.Bypass: in $procfs/self/status" + fi + fi + if [ -z "$kernel_ssb" ] && [ -n "$kernel" ]; then + kernel_ssb=$("${opt_arch_prefix}strings" "$kernel" | grep spec_store_bypass | head -n1); + [ -n "$kernel_ssb" ] && _debug "found $kernel_ssb in kernel" + fi + if [ -z "$kernel_ssb" ] && [ -n "$opt_map" ]; then + kernel_ssb=$(grep spec_store_bypass "$opt_map" | head -n1) + [ -n "$kernel_ssb" ] && _debug "found $kernel_ssb in System.map" + fi + + if [ -n "$kernel_ssb" ]; then + pstatus green YES "$kernel_ssb" + else + pstatus yellow NO + fi + + elif [ "$sys_interface_available" = 0 ]; then + # we have no sysfs but were asked to use it only! + msg="/sys vulnerability interface use forced, but it's not available!" + status=UNK + fi + + if ! is_cpu_vulnerable "$cve"; then + # override status & msg in case CPU is not vulnerable after all + pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" + elif [ -z "$msg" ] || [ "$msg" = "Vulnerable" ]; then + # if msg is empty, sysfs check didn't fill it, rely on our own test + if [ -n "$cpuid_ssbd" ]; then + if [ -n "$kernel_ssb" ]; then + pvulnstatus $cve OK "your system provides the necessary tools for software mitigation" + else + pvulnstatus $cve VULN "your kernel needs to be updated" + explain "You have a recent-enough CPU microcode but your kernel is too old to use the new features exported by your CPU's microcode. If you're using a distro kernel, upgrade your distro to get the latest kernel available. Otherwise, recompile the kernel from recent-enough sources." + fi + else + if [ -n "$kernel_ssb" ]; then + pvulnstatus $cve VULN "Your CPU doesn't support SSBD" + explain "Your kernel is recent enough to use the CPU microcode features for mitigation, but your CPU microcode doesn't actually provide the necessary features for the kernel to use. The microcode of your CPU hence needs to be upgraded. This is usually done at boot time by your kernel (the upgrade is not persistent across reboots which is why it's done at each boot). If you're using a distro, make sure you are up to date, as microcode updates are usually shipped alongside with the distro kernel. Availability of a microcode update for you CPU model depends on your CPU vendor. You can usually find out online if a microcode update is available for your CPU by searching for your CPUID (indicated in the Hardware Check section)." + else + pvulnstatus $cve VULN "Neither your CPU nor your kernel support SSBD" + explain "Both your CPU microcode and your kernel are lacking support for mitigation. If you're using a distro kernel, upgrade your distro to get the latest kernel available. Otherwise, recompile the kernel from recent-enough sources. The microcode of your CPU also needs to be upgraded. This is usually done at boot time by your kernel (the upgrade is not persistent across reboots which is why it's done at each boot). If you're using a distro, make sure you are up to date, as microcode updates are usually shipped alongside with the distro kernel. Availability of a microcode update for you CPU model depends on your CPU vendor. You can usually find out online if a microcode update is available for your CPU by searching for your CPUID (indicated in the Hardware Check section)." + fi + fi + else + pvulnstatus $cve "$status" "$msg" + fi +} + +check_CVE_2018_3639_bsd() +{ + _info_nol "* Kernel supports speculation store bypass: " + if sysctl hw.spec_store_bypass_disable >/dev/null 2>&1; then + kernel_ssb=1 + pstatus green YES + else + kernel_ssb=0 + pstatus yellow NO + fi + + _info_nol "* Speculation store bypass is administratively enabled: " + ssb_enabled=$(sysctl -n hw.spec_store_bypass_disable 2>/dev/null) + _debug "hw.spec_store_bypass_disable=$ssb_enabled" + case "$ssb_enabled" in + 0) pstatus yellow NO "disabled";; + 1) pstatus green YES "enabled";; + 2) pstatus green YES "auto mode";; + *) pstatus yellow NO "unavailable";; + esac + + _info_nol "* Speculation store bypass is currently active: " + ssb_active=$(sysctl -n hw.spec_store_bypass_disable_active 2>/dev/null) + _debug "hw.spec_store_bypass_disable_active=$ssb_active" + case "$ssb_active" in + 1) pstatus green YES;; + *) pstatus yellow NO;; + esac + + if ! is_cpu_vulnerable "$cve"; then + pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" + else + if [ "$ssb_active" = 1 ]; then + pvulnstatus $cve OK "SSBD mitigates the vulnerability" + elif [ -n "$cpuid_ssbd" ]; then + if [ "$kernel_ssb" = 1 ]; then + pvulnstatus $cve VULN "you need to enable ssbd through sysctl to mitigate the vulnerability" + else + pvulnstatus $cve VULN "your kernel needs to be updated" + fi + else + if [ "$kernel_ssb" = 1 ]; then + pvulnstatus $cve VULN "Your CPU doesn't support SSBD" + else + pvulnstatus $cve VULN "Neither your CPU nor your kernel support SSBD" + fi + fi + fi +} + +########################### +# L1TF / FORESHADOW SECTION + +# L1 terminal fault (SGX) aka 'Foreshadow' +check_CVE_2018_3615() +{ + cve='CVE-2018-3615' + _info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m" + + _info_nol "* CPU microcode mitigates the vulnerability: " + if [ "$cpu_flush_cmd" = 1 ] && [ "$cpuid_sgx" = 1 ]; then + # no easy way to detect a fixed SGX but we know that + # microcodes that have the FLUSH_CMD MSR also have the + # fixed SGX (for CPUs that support it) + pstatus green YES + elif [ "$cpuid_sgx" = 1 ]; then + pstatus red NO + else + pstatus blue N/A + fi + + if ! is_cpu_vulnerable "$cve"; then + # override status & msg in case CPU is not vulnerable after all + pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" + elif [ "$cpu_flush_cmd" = 1 ]; then + pvulnstatus $cve OK "your CPU microcode mitigates the vulnerability" + else + pvulnstatus $cve VULN "your CPU supports SGX and the microcode is not up to date" + fi +} + +# L1 terminal fault (OS) aka 'Foreshadow-NG (OS)' +check_CVE_2018_3620() +{ + cve='CVE-2018-3620' + _info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m" + if [ "$os" = Linux ]; then + check_CVE_2018_3620_linux + elif echo "$os" | grep -q BSD; then + check_CVE_2018_3620_bsd + else + _warn "Unsupported OS ($os)" + fi +} + +check_CVE_2018_3620_linux() +{ + status=UNK + sys_interface_available=0 + msg='' + if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/l1tf" '^[^;]+'; then + # this kernel has the /sys interface, trust it over everything + sys_interface_available=1 + fi + if [ "$opt_sysfs_only" != 1 ]; then + _info_nol "* Kernel supports PTE inversion: " + if ! which "${opt_arch_prefix}strings" >/dev/null 2>&1; then + pteinv_supported=-1 + else + if "${opt_arch_prefix}strings" "$kernel" | grep -Fq 'PTE Inversion'; then + pstatus green YES "found in kernel image" + _debug "pteinv: found pte inversion evidence in kernel image" + pteinv_supported=1 + else + pstatus yellow NO + pteinv_supported=0 + fi + fi + + _info_nol "* PTE inversion enabled and active: " + if [ "$opt_live" = 1 ]; then + if [ "$sys_interface_available" = 1 ]; then + if grep -q 'Mitigation: PTE Inversion' /sys/devices/system/cpu/vulnerabilities/l1tf; then + pstatus green YES + pteinv_active=1 + else + pstatus yellow NO + pteinv_active=0 + fi + else + pstatus yellow UNKNOWN "sysfs interface not available" + pteinv_active=-1 + fi + else + pstatus blue N/A "not testable in offline mode" + fi + elif [ "$sys_interface_available" = 0 ]; then + # we have no sysfs but were asked to use it only! + msg="/sys vulnerability interface use forced, but it's not available!" + status=UNK + fi + + if ! is_cpu_vulnerable "$cve"; then + # override status & msg in case CPU is not vulnerable after all + pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" + elif [ -z "$msg" ]; then + # if msg is empty, sysfs check didn't fill it, rely on our own test + if [ "$pteinv_supported" = 1 ]; then + if [ "$pteinv_active" = 1 ] || [ "$opt_live" != 1 ]; then + pvulnstatus $cve OK "PTE inversion mitigates the vunerability" + else + pvulnstatus $cve VULN "Your kernel supports PTE inversion but it doesn't seem to be enabled" + fi + else + pvulnstatus $cve VULN "Your kernel doesn't support PTE inversion, update it" + fi + else + pvulnstatus $cve "$status" "$msg" + fi +} + +check_CVE_2018_3620_bsd() +{ + _info_nol "* Kernel reserved the memory page at physical address 0x0: " + if sysctl hw.vmm.vmx.l1d_flush >/dev/null 2>&1; then + # https://security.FreeBSD.org/patches/SA-18:09/l1tf-11.2.patch + # this is very difficult to detect that the kernel reserved the 0 page, but this fix + # is part of the exact same patch than the other L1TF CVE, so we detect it + # and deem it as OK if the other patch is there + pstatus green YES + bsd_zero_reserved=1 + else + pstatus yellow NO + bsd_zero_reserved=0 + fi + + if ! is_cpu_vulnerable "$cve"; then + pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" + else + if [ "$bsd_zero_reserved" = 1 ]; then + pvulnstatus $cve OK "kernel mitigates the vulnerability" + else + pvulnstatus $cve VULN "your kernel needs to be updated" + fi + fi +} + +# L1TF VMM +check_CVE_2018_3646() +{ + cve='CVE-2018-3646' + _info "\033[1;34m$cve aka '$(cve2name "$cve")'\033[0m" + if [ "$os" = Linux ]; then + check_CVE_2018_3646_linux + elif echo "$os" | grep -q BSD; then + check_CVE_2018_3646_bsd + else + _warn "Unsupported OS ($os)" + fi +} + +check_CVE_2018_3646_linux() +{ + status=UNK + sys_interface_available=0 + msg='' + if sys_interface_check "/sys/devices/system/cpu/vulnerabilities/l1tf" 'VMX:.*' silent; then + # this kernel has the /sys interface, trust it over everything + sys_interface_available=1 + fi + if [ "$opt_sysfs_only" != 1 ]; then + _info_nol "* This system is a host running a hypervisor: " + has_vmm=$opt_vmm + if [ "$has_vmm" = -1 ]; then + # Assumed to be running on bare metal unless evidence of vm is found. + has_vmm=0 + # test for presence of hypervisor flag - definitive if set + if [ -e "$procfs/cpuinfo" ] && grep ^flags "$procfs/cpuinfo" | grep -qw hypervisor; then + has_vmm=1 + _debug "hypervisor: present - hypervisor flag set in $procfs/cpuinfo" + else + _debug "hypervisor: unknown - hypervisor flag not set in $procfs/cpuinfo" + fi + # test for kernel detected hypervisor + dmesg_grep "Hypervisor detected:" ; ret=$? + if [ $ret -eq 0 ]; then + _debug "hypervisor: present - found in dmesg: $dmesg_grepped" + has_vmm=1 + elif [ $ret -eq 2 ]; then + _debug "hypervisor: dmesg truncated" + fi + # test for kernel detected paravirtualization + dmesg_grep "Booting paravirtualized kernel on bare hardware" ; ret=$? + if [ $ret -eq 0 ]; then + _debug "hypervisor: not present (bare hardware)- found in dmesg: $dmesg_grepped" + elif [ $ret -eq 2 ]; then + _debug "hypervisor: dmesg truncated" + else + dmesg_grep "Booting paravirtualized kernel on" ; ret=$? + if [ $ret -eq 0 ]; then + _debug "hypervisor: present - found in dmesg: $dmesg_grepped" + has_vmm=1 + elif [ $ret -eq 2 ]; then + _debug "hypervisor: dmesg truncated" + fi + fi + fi + if [ "$has_vmm" = 0 ]; then + if [ "$opt_vmm" != -1 ]; then + pstatus green NO "forced from command line" + else + pstatus green NO + fi + else + if [ "$opt_vmm" != -1 ]; then + pstatus blue YES "forced from command line" + else + pstatus blue YES + fi + fi + + _info "* Mitigation 1 (KVM)" + _info_nol " * EPT is disabled: " + if [ "$opt_live" = 1 ]; then + if ! [ -r /sys/module/kvm_intel/parameters/ept ]; then + pstatus blue N/A "the kvm_intel module is not loaded" + elif [ "$(cat /sys/module/kvm_intel/parameters/ept)" = N ]; then + pstatus green YES + ept_disabled=1 + else + pstatus yellow NO + fi + else + pstatus blue N/A "not testable in offline mode" + fi + + _info "* Mitigation 2" + _info_nol " * L1D flush is supported by kernel: " + if [ "$opt_live" = 1 ] && grep -qw flush_l1d "$procfs/cpuinfo"; then + l1d_kernel="found flush_l1d in $procfs/cpuinfo" + fi + if [ -z "$l1d_kernel" ]; then + if ! which "${opt_arch_prefix}strings" >/dev/null 2>&1; then + l1d_kernel_err="missing '${opt_arch_prefix}strings' tool, please install it, usually it's in the binutils package" + elif [ -n "$kernel_err" ]; then + l1d_kernel_err="$kernel_err" + elif "${opt_arch_prefix}strings" "$kernel" | grep -qw flush_l1d; then + l1d_kernel='found flush_l1d in kernel image' + fi + fi + + if [ -n "$l1d_kernel" ]; then + pstatus green YES "$l1d_kernel" + elif [ -n "$l1d_kernel_err" ]; then + pstatus yellow UNKNOWN "$l1d_kernel_err" + else + pstatus yellow NO + fi + + _info_nol " * L1D flush enabled: " + if [ "$opt_live" = 1 ]; then + if [ -r "/sys/devices/system/cpu/vulnerabilities/l1tf" ]; then + # vanilla: VMX: $l1dstatus, SMT $smtstatus + # Red Hat: VMX: SMT $smtstatus, L1D $l1dstatus + # $l1dstatus is one of (auto|vulnerable|conditional cache flushes|cache flushes|EPT disabled|flush not necessary) + # $smtstatus is one of (vulnerable|disabled) + if grep -Eq '(VMX:|L1D) (EPT disabled|vulnerable|flush not necessary)' "/sys/devices/system/cpu/vulnerabilities/l1tf"; then + l1d_mode=0 + pstatus yellow NO + elif grep -Eq '(VMX:|L1D) conditional cache flushes' "/sys/devices/system/cpu/vulnerabilities/l1tf"; then + l1d_mode=1 + pstatus green YES "conditional flushes" + elif grep -Eq '(VMX:|L1D) cache flushes' "/sys/devices/system/cpu/vulnerabilities/l1tf"; then + l1d_mode=2 + pstatus green YES "unconditional flushes" + else + l1d_mode=-1 + pstatus yellow UNKNOWN "unrecognized mode" + fi + else + l1d_mode=-1 + pstatus yellow UNKNOWN "can't find or read /sys/devices/system/cpu/vulnerabilities/l1tf" + fi + else + l1d_mode=-1 + pstatus blue N/A "not testable in offline mode" + fi + + _info_nol " * Hardware-backed L1D flush supported: " + if [ "$opt_live" = 1 ]; then + if grep -qw flush_l1d "$procfs/cpuinfo"; then + pstatus green YES "performance impact of the mitigation will be greatly reduced" + else + pstatus blue NO "flush will be done in software, this is slower" + fi + else + pstatus blue N/A "not testable in offline mode" + fi + + _info_nol " * Hyper-Threading (SMT) is enabled: " + is_cpu_smt_enabled; smt_enabled=$? + if [ "$smt_enabled" = 0 ]; then + pstatus yellow YES + elif [ "$smt_enabled" = 1 ]; then + pstatus green NO + else + pstatus yellow UNKNOWN + fi + + elif [ "$sys_interface_available" = 0 ]; then + # we have no sysfs but were asked to use it only! + msg="/sys vulnerability interface use forced, but it's not available!" + status=UNK + l1d_mode=-1 + fi + + if ! is_cpu_vulnerable "$cve"; then + # override status & msg in case CPU is not vulnerable after all + pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" + elif [ "$has_vmm" = 0 ]; then + pvulnstatus $cve OK "this system is not running a hypervisor" + else + if [ "$ept_disabled" = 1 ]; then + pvulnstatus $cve OK "EPT is disabled which mitigates the vulnerability" + elif [ "$opt_paranoid" = 0 ]; then + if [ "$l1d_mode" -ge 1 ]; then + pvulnstatus $cve OK "L1D flushing is enabled and mitigates the vulnerability" + else + pvulnstatus $cve VULN "disable EPT or enabled L1D flushing to mitigate the vulnerability" + fi + else + if [ "$l1d_mode" -ge 2 ]; then + if [ "$smt_enabled" = 1 ]; then + pvulnstatus $cve OK "L1D unconditional flushing and Hyper-Threading disabled are mitigating the vulnerability" + else + pvulnstatus $cve VULN "Hyper-Threading must be disabled to fully mitigate the vulnerability" + fi + else + if [ "$smt_enabled" = 1 ]; then + pvulnstatus $cve VULN "L1D unconditional flushing should be enabled to fully mitigate the vulnerability" + else + pvulnstatus $cve VULN "enable L1D unconditional flushing and disable Hyper-Threading to fully mitigate the vulnerability" + fi + fi + fi + fi +} + +check_CVE_2018_3646_bsd() +{ + _info_nol "* Kernel supports L1D flushing: " + if sysctl hw.vmm.vmx.l1d_flush >/dev/null 2>&1; then + pstatus green YES + kernel_l1d_supported=1 + else + pstatus yellow NO + kernel_l1d_supported=0 + fi + + _info_nol "* L1D flushing is enabled: " + kernel_l1d_enabled=$(sysctl -n hw.vmm.vmx.l1d_flush 2>/dev/null) + case "$kernel_l1d_enabled" in + 0) pstatus yellow NO;; + 1) pstatus green YES;; + "") pstatus yellow NO;; + *) pstatus yellow UNKNOWN;; + esac + + if ! is_cpu_vulnerable "$cve"; then + pvulnstatus $cve OK "your CPU vendor reported your CPU model as not vulnerable" + else + if [ "$kernel_l1d_enabled" = 1 ]; then + pvulnstatus $cve OK "L1D flushing mitigates the vulnerability" + elif [ "$kernel_l1d_supported" = 1 ]; then + pvulnstatus $cve VULN "L1D flushing is supported by your kernel but is disabled" + else + pvulnstatus $cve VULN "your kernel needs to be updated" + fi + fi +} + if [ "$opt_no_hw" = 0 ] && [ -z "$opt_arch_prefix" ]; then check_cpu check_cpu_vulnerabilities @@ -2813,32 +3963,44 @@ if [ "$opt_no_hw" = 0 ] && [ -z "$opt_arch_prefix" ]; then fi # now run the checks the user asked for -if [ "$opt_variant1" = 1 ] || [ "$opt_allvariants" = 1 ]; then - check_variant1 - _info +for cve in $supported_cve_list +do + if [ "$opt_cve_all" = 1 ] || echo "$opt_cve_list" | grep -qw "$cve"; then + check_"$(echo "$cve" | tr - _)" + _info + fi +done + +if [ -n "$final_summary" ]; then + _info "> \033[46m\033[30mSUMMARY:\033[0m$final_summary" + _info "" fi -if [ "$opt_variant2" = 1 ] || [ "$opt_allvariants" = 1 ]; then - check_variant2 - _info -fi -if [ "$opt_variant3" = 1 ] || [ "$opt_allvariants" = 1 ]; then - check_variant3 - _info + +if [ "$bad_accuracy" = 1 ]; then + _warn "We're missing some kernel info (see -v), accuracy might be reduced" fi _vars=$(set | grep -Ev '^[A-Z_[:space:]]' | sort | tr "\n" '|') _debug "variables at end of script: $_vars" +if [ "$opt_explain" = 0 ]; then + _info "Need more detailed information about mitigation options? Use --explain" +fi + _info "A false sense of security is worse than no security at all, see --disclaimer" if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "nrpe" ]; then - if [ ! -z "$nrpe_vuln" ]; then + if [ -n "$nrpe_vuln" ]; then echo "Vulnerable:$nrpe_vuln" else echo "OK" fi fi +if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "short" ]; then + _echo 0 "${short_output% }" +fi + if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "json" ]; then _echo 0 "${json_output%?}]" fi @@ -2846,10 +4008,357 @@ fi if [ "$opt_batch" = 1 ] && [ "$opt_batch_format" = "prometheus" ]; then echo "# TYPE specex_vuln_status untyped" echo "# HELP specex_vuln_status Exposure of system to speculative execution vulnerabilities" - echo "$prometheus_output" + printf "%b\n" "$prometheus_output" fi # exit with the proper exit code [ "$global_critical" = 1 ] && exit 2 # critical [ "$global_unknown" = 1 ] && exit 3 # unknown exit 0 # ok + +# We're using MCE.db from the excellent platomav's MCExtractor project +# The builtin version follows, the user can update it with --update-mcedb + +# wget https://github.com/platomav/MCExtractor/raw/master/MCE.db +# sqlite3 MCE.db "select '%%% MCEDB v'||revision||' - '||strftime('%Y/%m/%d', date, 'unixepoch') from MCE; select '# I,0x'||cpuid||',0x'||version||','||max(yyyymmdd) from Intel group by cpuid order by cpuid asc; select '# A,0x'||cpuid||',0x'||version||','||max(yyyymmdd) from AMD group by cpuid order by cpuid asc" +# %%% MCEDB v84 - 2018/09/27 +# I,0x00000611,0x00000B27,19961218 +# I,0x00000612,0x000000C6,19961210 +# I,0x00000616,0x000000C6,19961210 +# I,0x00000617,0x000000C6,19961210 +# I,0x00000619,0x000000D2,19980218 +# I,0x00000630,0x00000013,19960827 +# I,0x00000632,0x00000020,19960903 +# I,0x00000633,0x00000036,19980923 +# I,0x00000634,0x00000037,19980923 +# I,0x00000650,0x00000040,19990525 +# I,0x00000651,0x00000040,19990525 +# I,0x00000652,0x0000002D,19990518 +# I,0x00000653,0x00000010,19990628 +# I,0x00000660,0x0000000A,19990505 +# I,0x00000665,0x00000003,19990505 +# I,0x0000066A,0x0000000C,19990505 +# I,0x0000066D,0x00000007,19990505 +# I,0x00000670,0x00000007,19980602 +# I,0x00000671,0x00000003,19980811 +# I,0x00000672,0x00000010,19990922 +# I,0x00000673,0x0000000E,19990910 +# I,0x00000680,0x00000014,19990610 +# I,0x00000681,0x00000014,19991209 +# I,0x00000683,0x00000013,20010206 +# I,0x00000686,0x00000007,20000505 +# I,0x0000068A,0x00000004,20001207 +# I,0x00000690,0x00000004,20000206 +# I,0x00000691,0x00000001,20020527 +# I,0x00000692,0x00000001,20020620 +# I,0x00000694,0x00000002,20020926 +# I,0x00000695,0x00000007,20041109 +# I,0x00000696,0x00000001,20000707 +# I,0x000006A0,0x00000003,20000110 +# I,0x000006A1,0x00000001,20000306 +# I,0x000006A4,0x00000001,20000616 +# I,0x000006B0,0x0000001A,20010129 +# I,0x000006B1,0x0000001D,20010220 +# I,0x000006B4,0x00000002,20020111 +# I,0x000006D0,0x00000006,20030522 +# I,0x000006D1,0x00000009,20030709 +# I,0x000006D2,0x00000010,20030814 +# I,0x000006D6,0x00000018,20041017 +# I,0x000006D8,0x00000021,20060831 +# I,0x000006E0,0x00000008,20050215 +# I,0x000006E1,0x0000000C,20050413 +# I,0x000006E4,0x00000026,20050816 +# I,0x000006E8,0x0000003C,20060208 +# I,0x000006EC,0x0000005B,20070208 +# I,0x000006F0,0x00000005,20050818 +# I,0x000006F1,0x00000012,20051129 +# I,0x000006F2,0x0000005D,20101002 +# I,0x000006F4,0x00000028,20060417 +# I,0x000006F5,0x00000039,20060727 +# I,0x000006F6,0x000000D2,20101001 +# I,0x000006F7,0x0000006A,20101002 +# I,0x000006F9,0x00000084,20061012 +# I,0x000006FA,0x00000095,20101002 +# I,0x000006FB,0x000000C1,20111004 +# I,0x000006FD,0x000000A4,20101002 +# I,0x00000F00,0xFFFF0001,20000130 +# I,0x00000F01,0xFFFF0007,20000404 +# I,0x00000F02,0xFFFF000B,20000518 +# I,0x00000F03,0xFFFF0001,20000518 +# I,0x00000F04,0xFFFF0010,20000803 +# I,0x00000F05,0x0000000B,20000824 +# I,0x00000F06,0x00000004,20000911 +# I,0x00000F07,0x00000012,20020716 +# I,0x00000F08,0x00000008,20001101 +# I,0x00000F09,0x00000008,20010104 +# I,0x00000F0A,0x00000015,20020821 +# I,0x00000F11,0x0000000A,20030729 +# I,0x00000F12,0x0000002D,20030502 +# I,0x00000F13,0x00000005,20030508 +# I,0x00000F20,0x00000001,20010423 +# I,0x00000F21,0x00000002,20010529 +# I,0x00000F22,0x00000005,20030729 +# I,0x00000F23,0x0000000D,20010817 +# I,0x00000F24,0x00000021,20030610 +# I,0x00000F25,0x0000002C,20040826 +# I,0x00000F26,0x00000010,20040805 +# I,0x00000F27,0x00000038,20030604 +# I,0x00000F29,0x0000002D,20040811 +# I,0x00000F30,0x00000013,20030815 +# I,0x00000F31,0x0000000B,20031021 +# I,0x00000F32,0x0000000A,20040511 +# I,0x00000F33,0x0000000C,20050421 +# I,0x00000F34,0x00000017,20050421 +# I,0x00000F36,0x00000007,20040309 +# I,0x00000F37,0x00000003,20031218 +# I,0x00000F40,0x00000006,20040318 +# I,0x00000F41,0x00000017,20050422 +# I,0x00000F42,0x00000003,20050421 +# I,0x00000F43,0x00000005,20050421 +# I,0x00000F44,0x00000006,20050421 +# I,0x00000F46,0x00000004,20050411 +# I,0x00000F47,0x00000003,20050421 +# I,0x00000F48,0x0000000E,20080115 +# I,0x00000F49,0x00000003,20050421 +# I,0x00000F4A,0x00000004,20051214 +# I,0x00000F60,0x00000005,20050124 +# I,0x00000F61,0x00000008,20050610 +# I,0x00000F62,0x0000000F,20051215 +# I,0x00000F63,0x00000005,20051010 +# I,0x00000F64,0x00000004,20051223 +# I,0x00000F65,0x0000000B,20070510 +# I,0x00000F66,0x0000001B,20060310 +# I,0x00000F68,0x00000009,20060714 +# I,0x00001632,0x00000002,19980610 +# I,0x00010650,0x00000002,20060513 +# I,0x00010660,0x00000004,20060612 +# I,0x00010661,0x00000043,20101004 +# I,0x00010670,0x00000005,20070209 +# I,0x00010671,0x00000106,20070329 +# I,0x00010674,0x84050100,20070726 +# I,0x00010676,0x00000612,20150802 +# I,0x00010677,0x0000070D,20150802 +# I,0x0001067A,0x00000A0E,20150729 +# I,0x000106A0,0xFFFF001A,20071128 +# I,0x000106A1,0xFFFF000B,20080220 +# I,0x000106A2,0xFFFF0019,20080714 +# I,0x000106A4,0x00000013,20150630 +# I,0x000106A5,0x0000001D,20180511 +# I,0x000106C0,0x00000007,20070824 +# I,0x000106C1,0x00000109,20071203 +# I,0x000106C2,0x00000217,20090410 +# I,0x000106C9,0x00000007,20090213 +# I,0x000106CA,0x00000107,20090825 +# I,0x000106D0,0x00000005,20071204 +# I,0x000106D1,0x0000002A,20150803 +# I,0x000106E0,0xFFFF0022,20090116 +# I,0x000106E1,0xFFFF000D,20090206 +# I,0x000106E3,0xFFFF0011,20090512 +# I,0x000106E4,0x00000003,20130701 +# I,0x000106E5,0x0000000A,20180508 +# I,0x000106F0,0xFFFF0009,20090210 +# I,0x000106F1,0xFFFF0007,20090210 +# I,0x00020650,0xFFFF0008,20090218 +# I,0x00020651,0xFFFF0018,20090818 +# I,0x00020652,0x00000011,20180508 +# I,0x00020654,0xFFFF0007,20091124 +# I,0x00020655,0x00000007,20180423 +# I,0x00020661,0x00000105,20110718 +# I,0x000206A0,0x00000029,20091102 +# I,0x000206A1,0x00000007,20091223 +# I,0x000206A2,0x00000027,20100502 +# I,0x000206A3,0x00000009,20100609 +# I,0x000206A4,0x00000022,20100414 +# I,0x000206A5,0x00000007,20100722 +# I,0x000206A6,0x90030028,20100924 +# I,0x000206A7,0x0000002E,20180410 +# I,0x000206C0,0xFFFF001C,20091214 +# I,0x000206C1,0x00000006,20091222 +# I,0x000206C2,0x0000001F,20180508 +# I,0x000206D0,0x80000006,20100816 +# I,0x000206D1,0x80000106,20101201 +# I,0x000206D2,0x9584020C,20110622 +# I,0x000206D3,0x80000304,20110420 +# I,0x000206D5,0x00000513,20111013 +# I,0x000206D6,0x0000061D,20180508 +# I,0x000206D7,0x00000714,20180508 +# I,0x000206E0,0xE3493401,20090108 +# I,0x000206E1,0xE3493402,20090224 +# I,0x000206E2,0xFFFF0004,20081001 +# I,0x000206E3,0xE4486547,20090701 +# I,0x000206E4,0xFFFF0008,20090619 +# I,0x000206E5,0xFFFF0018,20091215 +# I,0x000206E6,0x0000000D,20180515 +# I,0x000206F0,0x00000004,20100630 +# I,0x000206F1,0x00000008,20101013 +# I,0x000206F2,0x0000003B,20180516 +# I,0x00030650,0x00000009,20120118 +# I,0x00030651,0x00000110,20131014 +# I,0x00030660,0x00000003,20101103 +# I,0x00030661,0x0000010F,20150721 +# I,0x00030669,0x0000010D,20130515 +# I,0x00030671,0x00000117,20130410 +# I,0x00030672,0x0000022E,20140401 +# I,0x00030673,0x00000326,20180110 +# I,0x00030678,0x00000837,20180125 +# I,0x00030679,0x0000090A,20180110 +# I,0x000306A0,0x00000007,20110407 +# I,0x000306A2,0x0000000C,20110725 +# I,0x000306A4,0x00000007,20110908 +# I,0x000306A5,0x00000009,20111110 +# I,0x000306A6,0x00000004,20111114 +# I,0x000306A8,0x00000010,20120220 +# I,0x000306A9,0x00000020,20180410 +# I,0x000306C0,0xFFFF0013,20111110 +# I,0x000306C1,0xFFFF0014,20120725 +# I,0x000306C2,0xFFFF0006,20121017 +# I,0x000306C3,0x00000025,20180402 +# I,0x000306D1,0xFFFF0009,20131015 +# I,0x000306D2,0xFFFF0009,20131219 +# I,0x000306D3,0xE3121338,20140825 +# I,0x000306D4,0x0000002B,20180322 +# I,0x000306E0,0x00000008,20120726 +# I,0x000306E2,0x0000020D,20130321 +# I,0x000306E3,0x00000308,20130321 +# I,0x000306E4,0x0000042D,20180425 +# I,0x000306E6,0x00000600,20130619 +# I,0x000306E7,0x00000714,20180425 +# I,0x000306F0,0xFFFF0017,20130730 +# I,0x000306F1,0x00000014,20140110 +# I,0x000306F2,0x0000003D,20180420 +# I,0x000306F3,0x0000000D,20160211 +# I,0x000306F4,0x00000012,20180420 +# I,0x00040650,0xFFFF000B,20121206 +# I,0x00040651,0x00000024,20180402 +# I,0x00040660,0xFFFF0011,20121012 +# I,0x00040661,0x0000001A,20180402 +# I,0x00040670,0xFFFF0006,20140304 +# I,0x00040671,0x0000001E,20180403 +# I,0x000406A0,0x80124001,20130521 +# I,0x000406A8,0x0000081F,20140812 +# I,0x000406A9,0x0000081F,20140812 +# I,0x000406C1,0x0000010B,20140814 +# I,0x000406C2,0x00000221,20150218 +# I,0x000406C3,0x00000367,20171225 +# I,0x000406C4,0x00000410,20180104 +# I,0x000406D0,0x0000000E,20130612 +# I,0x000406D8,0x0000012A,20180104 +# I,0x000406E1,0x00000020,20141111 +# I,0x000406E2,0x0000002C,20150521 +# I,0x000406E3,0x000000C6,20180417 +# I,0x000406E8,0x00000026,20160414 +# I,0x000406F0,0x00000014,20150702 +# I,0x000406F1,0x0B00002E,20180419 +# I,0x00050650,0x8000002B,20160208 +# I,0x00050651,0x8000002B,20160208 +# I,0x00050652,0x80000037,20170502 +# I,0x00050653,0x01000144,20180420 +# I,0x00050654,0x0200004D,20180515 +# I,0x00050655,0x0300000B,20180427 +# I,0x00050661,0xF1000008,20150130 +# I,0x00050662,0x00000017,20180525 +# I,0x00050663,0x07000013,20180420 +# I,0x00050664,0x0F000012,20180420 +# I,0x00050665,0x0E00000A,20180420 +# I,0x00050670,0xFFFF0030,20151113 +# I,0x00050671,0x000001B6,20180108 +# I,0x000506A0,0x00000038,20150112 +# I,0x000506C2,0x00000014,20180511 +# I,0x000506C8,0x90011010,20160323 +# I,0x000506C9,0x00000032,20180511 +# I,0x000506CA,0x0000000C,20180511 +# I,0x000506D1,0x00000102,20150605 +# I,0x000506E0,0x00000018,20141119 +# I,0x000506E1,0x0000002A,20150602 +# I,0x000506E2,0x0000002E,20150815 +# I,0x000506E3,0x000000C6,20180417 +# I,0x000506E8,0x00000034,20160710 +# I,0x000506F1,0x00000024,20180511 +# I,0x00060660,0x0000000C,20160821 +# I,0x00060661,0x0000000E,20170128 +# I,0x00060662,0x00000022,20171129 +# I,0x00060663,0x0000002A,20180417 +# I,0x000706A0,0x00000026,20170712 +# I,0x000706A1,0x0000002A,20180725 +# I,0x00080650,0x00000018,20180108 +# I,0x000806E9,0x00000098,20180626 +# I,0x000806EA,0x00000096,20180515 +# I,0x000806EB,0x00000098,20180530 +# I,0x000906E9,0x0000008E,20180324 +# I,0x000906EA,0x00000096,20180502 +# I,0x000906EB,0x0000008E,20180324 +# I,0x000906EC,0x0000009E,20180826 +# A,0x00000F00,0x02000008,20070614 +# A,0x00000F01,0x0000001C,20021031 +# A,0x00000F10,0x00000003,20020325 +# A,0x00000F11,0x0000001F,20030220 +# A,0x00000F48,0x00000046,20040719 +# A,0x00000F4A,0x00000047,20040719 +# A,0x00000F50,0x00000024,20021212 +# A,0x00000F51,0x00000025,20030115 +# A,0x00010F50,0x00000041,20040225 +# A,0x00020F10,0x0000004D,20050428 +# A,0x00040F01,0xC0012102,20050916 +# A,0x00040F0A,0x00000068,20060920 +# A,0x00040F13,0x0000007A,20080508 +# A,0x00040F14,0x00000062,20060127 +# A,0x00040F1B,0x0000006D,20060920 +# A,0x00040F33,0x0000007B,20080514 +# A,0x00060F80,0x00000083,20060929 +# A,0x000C0F1B,0x0000006E,20060921 +# A,0x000F0F00,0x00000005,20020627 +# A,0x000F0F01,0x00000015,20020627 +# A,0x00100F00,0x01000020,20070326 +# A,0x00100F20,0x010000CA,20100331 +# A,0x00100F22,0x010000C9,20100331 +# A,0x00100F40,0x01000085,20080501 +# A,0x00100F41,0x010000DB,20111024 +# A,0x00100F42,0x01000092,20081021 +# A,0x00100F43,0x010000C8,20100311 +# A,0x00100F62,0x010000C7,20100311 +# A,0x00100F80,0x010000DA,20111024 +# A,0x00100F81,0x010000D9,20111012 +# A,0x00100FA0,0x010000DC,20111024 +# A,0x00120F00,0x03000002,20100324 +# A,0x00200F30,0x02000018,20070921 +# A,0x00200F31,0x02000057,20080502 +# A,0x00200F32,0x02000034,20080307 +# A,0x00300F01,0x0300000E,20101004 +# A,0x00300F10,0x03000027,20111309 +# A,0x00500F00,0x0500000B,20100601 +# A,0x00500F01,0x0500001A,20100908 +# A,0x00500F10,0x05000029,20130121 +# A,0x00500F20,0x05000119,20130118 +# A,0x00580F00,0x0500000B,20100601 +# A,0x00580F01,0x0500001A,20100908 +# A,0x00580F10,0x05000028,20101124 +# A,0x00580F20,0x05000101,20110406 +# A,0x00600F00,0x06000017,20101029 +# A,0x00600F01,0x0600011F,20110227 +# A,0x00600F10,0x06000425,20110408 +# A,0x00600F11,0x0600050D,20110627 +# A,0x00600F12,0x0600063E,20180207 +# A,0x00600F20,0x06000852,20180206 +# A,0x00610F00,0x0600100E,20111102 +# A,0x00610F01,0x0600111F,20180305 +# A,0x00630F00,0x0600301C,20130817 +# A,0x00630F01,0x06003109,20180227 +# A,0x00660F00,0x06006012,20141014 +# A,0x00660F01,0x0600611A,20180126 +# A,0x00670F00,0x06006705,20180220 +# A,0x00680F00,0x06000017,20101029 +# A,0x00680F01,0x0600011F,20110227 +# A,0x00680F10,0x06000410,20110314 +# A,0x00700F00,0x0700002A,20121218 +# A,0x00700F01,0x07000110,20180209 +# A,0x00730F00,0x07030009,20131206 +# A,0x00730F01,0x07030106,20180209 +# A,0x00800F00,0x0800002A,20161006 +# A,0x00800F10,0x0800100C,20170131 +# A,0x00800F11,0x08001137,20180214 +# A,0x00800F12,0x08001227,20180209 +# A,0x00800F82,0x0800820B,20180620 +# A,0x00810F00,0x08100004,20161120 +# A,0x00810F10,0x0810100B,20180212 +# A,0x00810F80,0x08108002,20180605 +# A,0x00820F00,0x08200002,20180214 From 51712f9c8a4f2cd30588497f98bb726a51ca1b6f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Jan 2019 17:16:11 +0000 Subject: [PATCH 119/721] 'yunohost' is a dumb password refused by postinstall, change it to a password that gets accepted --- src/yunohost/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 0c8445bd6..ed7799fc1 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -885,7 +885,7 @@ class RestoreManager(): raise YunohostError('backup_invalid_archive') logger.debug("executing the post-install...") - tools_postinstall(domain, 'yunohost', True) + tools_postinstall(domain, 'Yunohost', True) def clean(self): """ From 022d3922c1cc68da968755305086566079c73836 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Jan 2019 20:44:45 +0000 Subject: [PATCH 120/721] This is a list of url so should be called 'urls' --- data/helpers.d/setting | 14 +++++++------- .../data_migrations/0009_setup_group_permission.py | 2 +- src/yunohost/permission.py | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 585c7ccd0..852fbf993 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -28,26 +28,26 @@ ynh_app_setting_delete() { # Create a new permission for the app # -# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--url "url" ["url" ...]] +# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--urls "url" ["url" ...]] # | arg: app - the application id # | arg: permission - the name for the permission (by default a permission named "main" already exist) # | arg: defaultdisallow - define if all user will be allowed by default -# | arg: url - the url for the the permission +# | arg: urls - the list of urls for the the permission ynh_permission_create() { - declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=url= ) + declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=urls= ) local app local permission local defaultdisallow - local url + local urls ynh_handle_getopts_args "$@" if [[ -n ${defaultdisallow:-} ]]; then defaultdisallow=",default_allow=False" fi - if [[ -n ${url:-} ]]; then - url=",url=['${url//';'/"','"}']" + if [[ -n ${urls:-} ]]; then + urls=",urls=['${urls//';'/"','"}']" fi - yunohost tools shell -c "from yunohost.permission import permission_add; permission_add(auth, '$app', '$permission' ${defaultdisallow:-} ${url:-}, sync_perm=False)" + yunohost tools shell -c "from yunohost.permission import permission_add; permission_add(auth, '$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)" } # Remove a permission for the app (note that when the app is removed all permission is automatically removed) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 2dee0b070..2d3ef5905 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -76,7 +76,7 @@ def migrate_app_permission(auth, app=None): url = None if domain and path: url = domain + path - permission_add(auth, app, 'main', url=url, default_allow=True, sync_perm=False) + permission_add(auth, app, 'main', urls=[url], default_allow=True, sync_perm=False) if permission: allowed_group = permission.split(',') user_permission_add(auth, [app], 'main', group=allowed_group, sync_perm=False) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 421f1ff2f..e490c33bd 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -312,14 +312,14 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None, sync_ @is_unit_operation(['permission','app']) -def permission_add(operation_logger, auth, app, permission, url=None, default_allow=True, sync_perm=True): +def permission_add(operation_logger, auth, app, permission, urls=None, default_allow=True, sync_perm=True): """ Create a new permission for a specific application Keyword argument: app -- an application OR sftp, xmpp (metronome), mail permission -- name of the permission ("main" by default) - url -- list of url to specify for the permission + urls -- list of urls to specify for the permission """ from yunohost.domain import _normalize_domain_path @@ -348,11 +348,11 @@ def permission_add(operation_logger, auth, app, permission, url=None, default_al if default_allow: attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org' - if url: + if urls: attr_dict['URL'] = [] - for u in url: - domain = u[:u.index('/')] - path = u[u.index('/'):] + for url in urls: + domain = url[:url.index('/')] + path = url[url.index('/'):] domain, path = _normalize_domain_path(domain, path) attr_dict['URL'].append(domain + path) From 3a6f7cbc976998624f7a73db9425b712bcf4f6d4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 19 Jan 2019 21:07:06 +0000 Subject: [PATCH 121/721] Typo / wording --- locales/en.json | 28 +++++++++---------- .../0009_setup_group_permission.py | 6 ++-- src/yunohost/permission.py | 8 +++--- src/yunohost/user.py | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/locales/en.json b/locales/en.json index 5ddf14590..c58692f01 100644 --- a/locales/en.json +++ b/locales/en.json @@ -211,8 +211,8 @@ "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", - "group_alread_allowed": "Group '{group:s}' already allowed to access to permission '{permission:s}' for app '{app:s}'", - "group_alread_disallowed": "Group '{group:s}' already disallowed to access to permission '{permission:s}' for app '{app:s}'", + "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled for app '{app:s}'", + "group_already_disallowed": "Group '{group:s}' already has permissions '{permission:s}' disabled for app '{app:s}'", "group_name_already_exist": "Group {name:s} already exist", "group_created": "Group creation success", "group_creation_failed": "Group creation failed", @@ -220,9 +220,9 @@ "group_deletion_failed": "Group deletion failed", "group_deletion_not_allowed": "You are not allowed to remove the main group of the user {user:s}", "group_info_failed": "Group info failed", - "group_unknown": "Groupe {group:s} unknown", - "group_updated": "Groupe updated", - "group_update_failed": "groupe update failed", + "group_unknown": "Group {group:s} unknown", + "group_updated": "Group updated", + "group_update_failed": "Group update failed", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", "hook_list_by_invalid": "Invalid property to list hook by", @@ -274,7 +274,7 @@ "log_user_group_update": "Update '{}' group", "log_user_update": "Update information of '{}' user", "log_user_permission_add": "Update '{}' permission", - "log_user_permission_remove": "Update '{}' permisson", + "log_user_permission_remove": "Update '{}' permission", "log_tools_maindomain": "Make '{}' as main domain", "log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_migrations_migrate_backward": "Migrate backward", @@ -336,13 +336,13 @@ "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0009_backup_before_migration": "Make a backup of LDAP and apps settings before the migration", - "migration_0009_can_not_backup_before_migration": "The backup of the system before the migration failled. Migration failed. Error : {error:s}", + "migration_0009_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", "migration_0009_create_group": "Create group for each user.", "migration_0009_done": "Migration sucess. You are now able to use groups of user.", - "migration_0009_failled": "Migration failed.", - "migration_0009_LDAP_update_failled": "LDAP update failled. Error : {error:s}", + "migration_0009_failed": "Migration failed.", + "migration_0009_LDAP_update_failed": "LDAP update failed. Error: {error:s}", "migration_0009_migrate_permission": "Migrate permission from apps settings to LDAP", - "migration_0009_migration_failled_try_rollback": "Migration failed. Try to restore the system. Error : {error:s}", + "migration_0009_migration_failed_try_rollback": "Migration failed. Try to restore the system. Error: {error:s}", "migration_0009_rollback_success": "System restored.", "migration_0009_update_LDAP_database": "Update LDAP database for groups and permission support", "migration_0009_update_LDAP_schema": "Update LDAP schema", @@ -374,7 +374,7 @@ "mysql_db_creation_failed": "MySQL database creation failed", "mysql_db_init_failed": "MySQL database init failed", "mysql_db_initialized": "The MySQL database has been initialized", - "need_define_permission_before": "You need to redefine the permission by 'yunohost user permission add -u USER' before to remove an allowed group", + "need_define_permission_before": "You need to redefine the permission using 'yunohost user permission add -u USER' before removing an allowed group", "network_check_mx_ko": "DNS MX record is not set", "network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network", "network_check_smtp_ok": "Outbound mail (SMTP port 25) is not blocked", @@ -411,7 +411,7 @@ "permission_already_clear": "Permission '{permission:s}' already clear for app {app:s}", "permission_already_exist": "Permission '{permission:s}' for app {app:s} already exist", "permission_created": "Permission '{permission:s}' for app {app:s} created", - "permission_creation_failled": "Permission creation failed", + "permission_creation_failed": "Permission creation failed", "permission_deleted": "Permission '{permission:s}' for app {app:s} deleted", "permission_deletion_failed": "Permission '{permission:s}' for app {app:s} deletion failed", "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", @@ -419,7 +419,7 @@ "permission_update_failed": "Permission update failed", "permission_generated": "Permission updated", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", - "permission_update_nothing_to_do": "Permission update nothing to do", + "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_available": "Port {port:d} is available", @@ -527,7 +527,7 @@ "upnp_disabled": "UPnP has been disabled", "upnp_enabled": "UPnP has been enabled", "upnp_port_open_failed": "Unable to open UPnP ports", - "user_alread_in_group": "User {user:} already in group {group:s}", + "user_already_in_group": "User {user:} already in group {group:s}", "user_created": "The user has been created", "user_creation_failed": "Unable to create user", "user_deleted": "The user has been deleted", diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 2d3ef5905..2baa5f465 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -42,7 +42,7 @@ def migrate_LDAP_db(auth): for rdn, attr_dict in ldap_map['depends_children'].items(): auth.add(rdn, attr_dict) except Exception as e: - raise YunohostError("migration_0009_LDAP_update_failled", error=e) + raise YunohostError("migration_0009_LDAP_update_failed", error=e) logger.info(m18n.n("migration_0009_create_group")) @@ -126,7 +126,7 @@ class MyMigration(Migration): permission_sync_to_user(auth) except Exception as e: - logger.warn(m18n.n("migration_0009_migration_failled_try_rollback", error=e)) + logger.warn(m18n.n("migration_0009_migration_failed_try_rollback", error=e)) os.system("systemctl stop slapd") os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) @@ -135,7 +135,7 @@ class MyMigration(Migration): os.system("systemctl start slapd") os.system("rm -r " + backup_folder) logger.info(m18n.n("migration_0009_rollback_success")) - raise YunohostError("migration_0009_failled") + raise YunohostError("migration_0009_failed") os.system("rm -r " + backup_folder) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index e490c33bd..61615cbb5 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -204,7 +204,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ raise YunohostError('need_define_permission_before') group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' if not group_name in new_per_dict[permission_name]: - logger.warning(m18n.n('group_alread_disallowed', permission=per, app=a, group=g)) + logger.warning(m18n.n('group_already_disallowed', permission=per, app=a, group=g)) else: new_per_dict[permission_name].remove(group_name) @@ -213,7 +213,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ for g in add_group: group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' if group_name in new_per_dict[permission_name]: - logger.warning(m18n.n('group_alread_allowed', permission=per, app=a, group=g)) + logger.warning(m18n.n('group_already_allowed', permission=per, app=a, group=g)) else: new_per_dict[permission_name].add(group_name) @@ -363,7 +363,7 @@ def permission_add(operation_logger, auth, app, permission, urls=None, default_a logger.success(m18n.n('permission_created', permission=permission, app=app)) return user_permission_list(auth, app, permission) - raise YunohostError('permission_creation_failled') + raise YunohostError('permission_creation_failed') @is_unit_operation(['permission','app']) @@ -418,7 +418,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem logger.success(m18n.n('permission_updated', permission=permission, app=app)) return user_permission_list(auth, app, permission) - raise YunohostError('premission_update_failled') + raise YunohostError('premission_update_failed') @is_unit_operation(['permission','app']) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 5e58909f7..442f499d3 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -648,7 +648,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u raise YunohostError('user_unknown', user=user) userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if userDN in group['member']: - logger.warning(m18n.n('user_alread_in_group', user=user, group=groupname)) + logger.warning(m18n.n('user_already_in_group', user=user, group=groupname)) new_group_list['member'].add(userDN) if remove_user: From 546edc2751ff18db8e299c61667f4962e9fa4c67 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 20 Jan 2019 00:44:43 +0100 Subject: [PATCH 122/721] [fix] Missing string for key dyndns_could_not_check_available --- locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.json b/locales/en.json index 5ef9f5c0c..7292f56e2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -168,6 +168,7 @@ "done": "Done", "downloading": "Downloading…", "dyndns_could_not_check_provide": "Could not check if {provider:s} can provide {domain:s}.", + "dyndns_could_not_check_available": "Could not check if {domain:s} is available on {provider:s}.", "dyndns_cron_installed": "The DynDNS cron job has been installed", "dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job", "dyndns_cron_removed": "The DynDNS cron job has been removed", From aa3a137f3df24112a483e5d0636278dbebb5d3ee Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 21 Jan 2019 20:24:02 +0100 Subject: [PATCH 123/721] Use getopts --- data/helpers.d/user | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index 43e4902b3..543be1685 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -42,31 +42,40 @@ ynh_system_user_exists() { # Create a system user # # examples: -# - ynh_system_user_create nextcloud -> creates a nextcloud user with +# - ynh_system_user_create --username=nextcloud -> creates a nextcloud user with # no home directory and /usr/sbin/nologin login shell (hence no login capability) -# - ynh_system_user_create discourse /var/www/discourse 1 --> creates a +# - ynh_system_user_create --username=discourse --home_dir=/var/www/discourse --use_shell --> creates a # discourse user using /var/www/discourse as home directory and the default login shell # -# usage: ynh_system_user_create user_name [home_dir [use_shell]] -# | arg: user_name - Name of the system user that will be create -# | arg: home_dir - Path of the home dir for the user. Usually the final path -# of the app. If this argument is omitted, the user will be created without home -# | arg: use_shell - Create a user using the default login shell if present. +# usage: ynh_system_user_create --username=user_name [--home_dir=home_dir] [--use_shell] +# | arg: -u, --username - Name of the system user that will be create +# | arg: -h, --home_dir - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home +# | arg: -s, --use_shell - Create a user using the default login shell if present. # If this argument is omitted, the user will be created with /usr/sbin/nologin shell ynh_system_user_create () { - if ! ynh_system_user_exists "$1" # Check if the user exists on the system + # Declare an array to define the options of this helper. + local legacy_args=uh + declare -Ar args_array=( [u]=username= [h]=home_dir= [s]=use_shell ) + local username + local home_dir + local use_shell + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + local use_shell="${use_shell:-0}" + + if ! ynh_system_user_exists "$username" # Check if the user exists on the system then # If the user doesn't exist - if [ $# -ge 2 ]; then # If a home dir is mentioned - local user_home_dir="-d $2" + if [ -n "$home_dir" ]; then # If a home dir is mentioned + local user_home_dir="-d $home_dir" else local user_home_dir="--no-create-home" fi - if [ $# -ge 3 ]; then # If we want a shell for the user + if [ $use_shell -eq 1 ]; then # If we want a shell for the user local shell="" # Use default shell else local shell="--shell /usr/sbin/nologin" fi - useradd $user_home_dir --system --user-group $1 $shell || ynh_die "Unable to create $1 system account" + useradd $user_home_dir --system --user-group $username $shell || ynh_die "Unable to create $username system account" fi } From 08869c329c5ac58645544f5672d53ab61927fa7b Mon Sep 17 00:00:00 2001 From: frju365 Date: Mon, 21 Jan 2019 21:17:33 +0100 Subject: [PATCH 124/721] Update yunohost_admin.conf --- data/templates/nginx/plain/yunohost_admin.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index 3de66e3e6..06d1ef09c 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -68,7 +68,7 @@ server { if ($http_user_agent ~ (crawl|Googlebot|Slurp|spider|bingbot|tracker|click|parser|spider|facebookexternalhit) ) { return 403; } - + add_header X-Robots-Tag "nofollow, noindex, noarchive, nosnippet"; # Redirect most of 404 to maindomain.tld/yunohost/sso access_by_lua_file /usr/share/ssowat/access.lua; } From a42df2d8fe3371b5e1d5c0a7a325fa8524e7fe28 Mon Sep 17 00:00:00 2001 From: frju365 Date: Mon, 21 Jan 2019 21:39:07 +0100 Subject: [PATCH 125/721] Update yunohost_admin.conf --- data/templates/nginx/plain/yunohost_admin.conf | 3 --- 1 file changed, 3 deletions(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index 06d1ef09c..917ad0d5e 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -65,9 +65,6 @@ server { location /yunohost { # Block crawlers bot - if ($http_user_agent ~ (crawl|Googlebot|Slurp|spider|bingbot|tracker|click|parser|spider|facebookexternalhit) ) { - return 403; - } add_header X-Robots-Tag "nofollow, noindex, noarchive, nosnippet"; # Redirect most of 404 to maindomain.tld/yunohost/sso access_by_lua_file /usr/share/ssowat/access.lua; From ddbe11035d6f00476e06c454175a0382eb3c4c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Bourr=C3=A9?= Date: Tue, 22 Jan 2019 01:24:57 +0100 Subject: [PATCH 126/721] add systemd logs for apps --- src/yunohost/service.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 302ad651e..42fe4fc5d 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -375,7 +375,10 @@ def service_log(name, number=50): for log_path in log_list: # log is a file, read it - if not os.path.isdir(log_path): + if log_path == "systemd": + result[log_path] = _get_journalctl_logs(name) + continue + elif not os.path.isdir(log_path): result[log_path] = _tail(log_path, int(number)) if os.path.exists(log_path) else [] continue From a2935dc44b89f553d54cdc468a29f8c9564d28a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Bourr=C3=A9?= Date: Tue, 22 Jan 2019 01:50:16 +0100 Subject: [PATCH 127/721] limit logs to a number --- src/yunohost/service.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 42fe4fc5d..6d055aa52 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -376,7 +376,7 @@ def service_log(name, number=50): for log_path in log_list: # log is a file, read it if log_path == "systemd": - result[log_path] = _get_journalctl_logs(name) + result[log_path] = _get_journalctl_logs(name, int(number)) continue elif not os.path.isdir(log_path): result[log_path] = _tail(log_path, int(number)) if os.path.exists(log_path) else [] @@ -1050,9 +1050,9 @@ def manually_modified_files(): return output -def _get_journalctl_logs(service): +def _get_journalctl_logs(service, number="all"): try: - return subprocess.check_output("journalctl -xn -u %s" % service, shell=True) + return subprocess.check_output("journalctl -xn -u {0} -n{1}".format(service, number), shell=True) except: import traceback return "error while get services logs from journalctl:\n%s" % traceback.format_exc() From 66acd35c80b3d5f087e537443a5c3154c2d50623 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 22 Jan 2019 13:19:34 +0100 Subject: [PATCH 128/721] [fix] Restore warning in backup The warning for a non existing file during the backup disappear during a [conflict resolve](https://github.com/YunoHost/yunohost/pull/576/commits/72159f702f1a2af9249e973a53b4d165a4829b86)... I restore it as a micro decision. --- data/helpers.d/filesystem | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 5b55b752f..dfea026b6 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -64,6 +64,8 @@ ynh_backup() { [[ -e "${SRC_PATH}" ]] || { if [ "$NOT_MANDATORY" == "0" ] then + echo "Source path '${SRC_PATH}' does not exist" >&2 + # This is a temporary fix for fail2ban config files missing after the migration to stretch. if echo "${SRC_PATH}" | grep --quiet "/etc/fail2ban" then From 270bfb54cd3a0f43a2b78a64efe2742c88f31726 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 22 Jan 2019 16:26:54 +0100 Subject: [PATCH 129/721] This message should rather be an info --- src/yunohost/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 189b1db09..92ed086bf 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -868,7 +868,7 @@ def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclai # no new migrations to run if target == last_run_migration_number: - logger.warn(m18n.n('migrations_no_migrations_to_run')) + logger.info(m18n.n('migrations_no_migrations_to_run')) return logger.debug(m18n.n('migrations_show_last_migration', last_run_migration_number)) From 6d76ef95f4e0204607e17e4d2b70c7d3e4914936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Bourr=C3=A9?= Date: Tue, 22 Jan 2019 23:07:01 +0100 Subject: [PATCH 130/721] array for the admin panel --- src/yunohost/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 6d055aa52..63e46de81 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -376,7 +376,7 @@ def service_log(name, number=50): for log_path in log_list: # log is a file, read it if log_path == "systemd": - result[log_path] = _get_journalctl_logs(name, int(number)) + result[log_path] = _get_journalctl_logs(name, int(number)).splitlines() continue elif not os.path.isdir(log_path): result[log_path] = _tail(log_path, int(number)) if os.path.exists(log_path) else [] From dffecf4ae6764df9556de43eb28c703403725740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Bourr=C3=A9?= Date: Wed, 23 Jan 2019 00:00:25 +0100 Subject: [PATCH 131/721] add actionsmap type_log to precise if the log is a file or a systemd log --- data/actionsmap/yunohost.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 38e311546..c317be2a1 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1149,6 +1149,14 @@ service: -d: full: --description help: Description of the service + -t: + full: --type_log + help: Type of the log (file or systemd) + nargs: "+" + choices: + - file + - systemd + default: file ### service_remove() remove: From 836ec050fbfd307b43981fe41ec9926b7feba717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Bourr=C3=A9?= Date: Wed, 23 Jan 2019 00:03:36 +0100 Subject: [PATCH 132/721] add the systemd logs --- src/yunohost/service.py | 56 +++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 63e46de81..52f4ac682 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -49,7 +49,7 @@ MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" logger = log.getActionLogger('yunohost.service') -def service_add(name, status=None, log=None, runlevel=None, need_lock=False, description=None): +def service_add(name, status=None, log=None, runlevel=None, need_lock=False, description=None, type_log="file"): """ Add a custom service @@ -60,6 +60,7 @@ def service_add(name, status=None, log=None, runlevel=None, need_lock=False, des runlevel -- Runlevel priority of the service need_lock -- Use this option to prevent deadlocks if the service does invoke yunohost commands. description -- description of the service + type_log -- Precise if the corresponding log is a file or a systemd log """ services = _get_services() @@ -69,8 +70,23 @@ def service_add(name, status=None, log=None, runlevel=None, need_lock=False, des services[name] = {'status': status} if log is not None: + if not isinstance(log, list): + log = [log] + services[name]['log'] = log + if not isinstance(type_log, list): + type_log = [type_log] + + if len(type_log) < len(log): + type_log.extend([type_log[-1]] * (len(log) - len(type_log))) # extend list to have the same size as log + + if len(type_log) == len(log): + services[name]['type_log'] = type_log + else: + raise YunohostError('service_add_failed', service=name) + + if runlevel is not None: services[name]['runlevel'] = runlevel @@ -367,31 +383,33 @@ def service_log(name, number=50): raise YunohostError('service_no_log', service=name) log_list = services[name]['log'] - - if not isinstance(log_list, list): - log_list = [log_list] + type_log_list = services[name]['type_log'] result = {} - for log_path in log_list: - # log is a file, read it - if log_path == "systemd": - result[log_path] = _get_journalctl_logs(name, int(number)).splitlines() - continue - elif not os.path.isdir(log_path): - result[log_path] = _tail(log_path, int(number)) if os.path.exists(log_path) else [] - continue + for index in range(len(log_list)): + log_path = log_list[index] + log_type = type_log_list[index] - for log_file in os.listdir(log_path): - log_file_path = os.path.join(log_path, log_file) - # not a file : skip - if not os.path.isfile(log_file_path): + if log_type == "file": + # log is a file, read it + if not os.path.isdir(log_path): + result[log_path] = _tail(log_path, int(number)) if os.path.exists(log_path) else [] continue - if not log_file.endswith(".log"): - continue + for log_file in os.listdir(log_path): + log_file_path = os.path.join(log_path, log_file) + # not a file : skip + if not os.path.isfile(log_file_path): + continue - result[log_file_path] = _tail(log_file_path, int(number)) if os.path.exists(log_file_path) else [] + if not log_file.endswith(".log"): + continue + + result[log_file_path] = _tail(log_file_path, int(number)) if os.path.exists(log_file_path) else [] + else: + # get log with journalctl + result[log_path] = _get_journalctl_logs(log_path, int(number)).splitlines() return result From bbb004e056fd8de1ebe8de5ac8b1ec0b58d55025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Bourr=C3=A9?= Date: Wed, 23 Jan 2019 00:50:45 +0100 Subject: [PATCH 133/721] fix typo --- data/actionsmap/yunohost.yml | 2 +- src/yunohost/service.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index c317be2a1..cbe959b55 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1150,7 +1150,7 @@ service: full: --description help: Description of the service -t: - full: --type_log + full: --log_type help: Type of the log (file or systemd) nargs: "+" choices: diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 52f4ac682..e586f0642 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -49,7 +49,7 @@ MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" logger = log.getActionLogger('yunohost.service') -def service_add(name, status=None, log=None, runlevel=None, need_lock=False, description=None, type_log="file"): +def service_add(name, status=None, log=None, runlevel=None, need_lock=False, description=None, log_type="file"): """ Add a custom service @@ -60,7 +60,7 @@ def service_add(name, status=None, log=None, runlevel=None, need_lock=False, des runlevel -- Runlevel priority of the service need_lock -- Use this option to prevent deadlocks if the service does invoke yunohost commands. description -- description of the service - type_log -- Precise if the corresponding log is a file or a systemd log + log_type -- Precise if the corresponding log is a file or a systemd log """ services = _get_services() @@ -75,14 +75,14 @@ def service_add(name, status=None, log=None, runlevel=None, need_lock=False, des services[name]['log'] = log - if not isinstance(type_log, list): - type_log = [type_log] + if not isinstance(log_type, list): + log_type = [log_type] - if len(type_log) < len(log): - type_log.extend([type_log[-1]] * (len(log) - len(type_log))) # extend list to have the same size as log + if len(log_type) < len(log): + log_type.extend([log_type[-1]] * (len(log) - len(log_type))) # extend list to have the same size as log - if len(type_log) == len(log): - services[name]['type_log'] = type_log + if len(log_type) == len(log): + services[name]['log_type'] = log_type else: raise YunohostError('service_add_failed', service=name) @@ -383,13 +383,13 @@ def service_log(name, number=50): raise YunohostError('service_no_log', service=name) log_list = services[name]['log'] - type_log_list = services[name]['type_log'] + log_type_list = services[name]['log_type'] result = {} for index in range(len(log_list)): log_path = log_list[index] - log_type = type_log_list[index] + log_type = log_type_list[index] if log_type == "file": # log is a file, read it From 7bbd30b3cfc7d0d396c7c50ba2a3b82fcc48db33 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Thu, 24 Jan 2019 00:41:41 +0100 Subject: [PATCH 134/721] [enh] Keep it fucking clear... In order to keep the code clear in case of failure, one should not pile up command with logical AND. By keeping each command on its line, we can know which line fails. --- data/helpers.d/package | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/package b/data/helpers.d/package index 8b672d701..2cbca4840 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -131,11 +131,11 @@ ynh_package_install_from_equivs () { # Install the fake package without its dependencies with dpkg # Install missing dependencies with ynh_package_install ynh_wait_dpkg_free - (cp "$controlfile" "${TMPDIR}/control" && cd "$TMPDIR" \ - && equivs-build ./control 1>/dev/null \ - && sudo dpkg --force-depends \ - -i "./${pkgname}_${pkgversion}_all.deb" 2>&1 \ - && ynh_package_install -f) || ynh_die "Unable to install dependencies" + cp "$controlfile" "${TMPDIR}/control" + (cd "$TMPDIR" + equivs-build ./control 1> /dev/null + dpkg --force-depends -i "./${pkgname}_${pkgversion}_all.deb" 2>&1) + ynh_package_install -f || ynh_die "Unable to install dependencies" [[ -n "$TMPDIR" ]] && rm -rf $TMPDIR # Remove the temp dir. # check if the package is actually installed From 02e6c1241fb284f6a2e012a00ff1fa84dfe144dd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 24 Jan 2019 17:02:42 +0100 Subject: [PATCH 135/721] [fix] app conflicting with itself during change_url --- src/yunohost/app.py | 2 +- src/yunohost/domain.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3925a86db..6f9402f83 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -465,7 +465,7 @@ def app_change_url(operation_logger, auth, app, domain, path): raise YunohostError("app_change_url_identical_domains", domain=domain, path=path) # Check the url is available - conflicts = _get_conflicting_apps(auth, domain, path) + conflicts = _get_conflicting_apps(auth, domain, path, ignore_app=app) if conflicts: apps = [] for path, app_id, app_label in conflicts: diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 16d391168..3d46691f8 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -226,13 +226,14 @@ def domain_cert_renew(auth, domain_list, force=False, no_checks=False, email=Fal return yunohost.certificate.certificate_renew(auth, domain_list, force, no_checks, email, staging) -def _get_conflicting_apps(auth, domain, path): +def _get_conflicting_apps(auth, domain, path, ignore_app=None): """ Return a list of all conflicting apps with a domain/path (it can be empty) Keyword argument: domain -- The domain for the web path (e.g. your.domain.tld) path -- The path to check (e.g. /coffee) + ignore_app -- An optional app id to ignore (c.f. the change_url usecase) """ domain, path = _normalize_domain_path(domain, path) @@ -253,6 +254,8 @@ def _get_conflicting_apps(auth, domain, path): if domain in apps_map: # Loop through apps for p, a in apps_map[domain].items(): + if a["id"] == ignore_app: + continue if path == p: conflicts.append((p, a["id"], a["label"])) # We also don't want conflicts with other apps starting with From 37810a22a277ccb8fff8011bd47ad7a828c90d88 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Thu, 24 Jan 2019 18:20:40 +0100 Subject: [PATCH 136/721] Fix #548 --- data/helpers.d/backend | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 26e53ede9..58bf65840 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -222,7 +222,12 @@ ynh_add_fpm_config () { if [ -e "../conf/php-fpm.ini" ] then - echo "Please do not use a separate ini file, merge you directives in the pool file instead." &>2 + echo "Please do not use a separate ini file, merge your directives in the pool file instead." >&2 + finalphpini="$fpm_config_dir/conf.d/20-$app.ini" + ynh_backup_if_checksum_is_different "$finalphpini" + sudo cp ../conf/php-fpm.ini "$finalphpini" + sudo chown root: "$finalphpini" + ynh_store_file_checksum "$finalphpini" fi sudo systemctl reload $fpm_service } From cd993ff06581c7600ca74eb0d865789650fb5548 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Thu, 24 Jan 2019 18:32:52 +0100 Subject: [PATCH 137/721] Print backup and restore logs into the upgrade log Without --debug in upgrade command line, no more log will be printed in stdout. Still the log will be added to the log file, and so will help to debug a backup in that case. --- data/helpers.d/utils | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index b280c3b21..43dabfcd4 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -48,7 +48,7 @@ ynh_restore_upgradebackup () { # Remove the application then restore it sudo yunohost app remove $app # Restore the backup - sudo yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force + sudo yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force --debug ynh_die "The app was restored to the way it was before the failed upgrade." fi else @@ -87,7 +87,7 @@ ynh_backup_before_upgrade () { fi # Create backup - sudo BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number + sudo BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number --debug if [ "$?" -eq 0 ] then # If the backup succeeded, remove the previous backup From e706daa9b9a94aa094b7f391b8452160eb30cf17 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 24 Jan 2019 18:33:35 +0100 Subject: [PATCH 138/721] Update data/helpers.d/backend Co-Authored-By: maniackcrudelis --- data/helpers.d/backend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 58bf65840..e73644d2d 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -222,7 +222,7 @@ ynh_add_fpm_config () { if [ -e "../conf/php-fpm.ini" ] then - echo "Please do not use a separate ini file, merge your directives in the pool file instead." >&2 + echo "Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." >&2 finalphpini="$fpm_config_dir/conf.d/20-$app.ini" ynh_backup_if_checksum_is_different "$finalphpini" sudo cp ../conf/php-fpm.ini "$finalphpini" From 969577b4c23721e1dfedc263cbb0b90f7927ad92 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 26 Jan 2019 19:43:40 +0100 Subject: [PATCH 139/721] Fix an issue where postgresql-9.4 was being detected as installed whereas it was in fact not --- src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py b/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py index c4a6e7f34..5ae729b60 100644 --- a/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py +++ b/src/yunohost/data_migrations/0005_postgresql_9p4_to_9p6.py @@ -38,6 +38,6 @@ class MyMigration(Migration): def package_is_installed(self, package_name): - p = subprocess.Popen("dpkg --list | grep -q -w {}".format(package_name), shell=True) + p = subprocess.Popen("dpkg --list | grep '^ii ' | grep -q -w {}".format(package_name), shell=True) p.communicate() return p.returncode == 0 From 8ec7d361a4888f197695e89dffa823248ef7c747 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 26 Jan 2019 20:11:17 +0100 Subject: [PATCH 140/721] Old comment from when http2 was disabled --- data/templates/nginx/plain/yunohost_admin.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index 5e7679b7d..b6fabf8e3 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -12,7 +12,6 @@ server { } server { - # Disabling http2 for now as it's causing weird issues with curl listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; From e379f4e3db4b2c5c5fe064b7fed2bf95692ca600 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 26 Jan 2019 23:32:07 +0100 Subject: [PATCH 141/721] Fix handling of old services with empty log_type --- src/yunohost/service.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index e586f0642..8f7ae7db3 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -72,7 +72,7 @@ def service_add(name, status=None, log=None, runlevel=None, need_lock=False, des if log is not None: if not isinstance(log, list): log = [log] - + services[name]['log'] = log if not isinstance(log_type, list): @@ -85,8 +85,8 @@ def service_add(name, status=None, log=None, runlevel=None, need_lock=False, des services[name]['log_type'] = log_type else: raise YunohostError('service_add_failed', service=name) - - + + if runlevel is not None: services[name]['runlevel'] = runlevel @@ -383,7 +383,12 @@ def service_log(name, number=50): raise YunohostError('service_no_log', service=name) log_list = services[name]['log'] - log_type_list = services[name]['log_type'] + log_type_list = services[name].get('log_type', []) + + if not isinstance(log_list, list): + log_list = [log_list] + if len(log_type_list) < len(log_list): + log_type_list.extend(["file"] * (len(log_list)-len(log_type_list))) result = {} From 723ff2eebb5949bc4b38529f157fa58de621bef5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 26 Jan 2019 23:33:38 +0100 Subject: [PATCH 142/721] More elegant loop --- src/yunohost/service.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 8f7ae7db3..60729053b 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -392,8 +392,7 @@ def service_log(name, number=50): result = {} - for index in range(len(log_list)): - log_path = log_list[index] + for index, log_path in enumerate(log_list): log_type = log_type_list[index] if log_type == "file": From 7b7030544876178e606c389606766eff89da21c4 Mon Sep 17 00:00:00 2001 From: frju365 Date: Sat, 26 Jan 2019 23:51:22 +0100 Subject: [PATCH 143/721] Update yunohost_admin.conf --- data/templates/nginx/plain/yunohost_admin.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index 917ad0d5e..24ea25072 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -65,6 +65,10 @@ server { location /yunohost { # Block crawlers bot + if ($http_user_agent ~ (crawl|Googlebot|Slurp|spider|bingbot|tracker|click|parser|spider|facebookexternalhit) ) { + return 403; + } + # X-Robots-Tag to precise the rules applied. add_header X-Robots-Tag "nofollow, noindex, noarchive, nosnippet"; # Redirect most of 404 to maindomain.tld/yunohost/sso access_by_lua_file /usr/share/ssowat/access.lua; From 8ffd7e87f5d45f2da725df3bb3cb64978adff23e Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 27 Jan 2019 23:28:43 +0100 Subject: [PATCH 144/721] [fix] Update ynh_handle_getopts_args Fix ynh_handle_getopts_args from https://github.com/YunoHost/yunohost/pull/561 - https://github.com/YunoHost/yunohost/commit/2094557e1e32f7b8d56406d4229fcd8e1f04f1fd#diff-a1f538c2eae234f6ba78e0bed0ae54db - https://github.com/YunoHost/yunohost/commit/976f160afbf34be27b04cd0bc1dc13870d642848#diff-a1f538c2eae234f6ba78e0bed0ae54db - https://github.com/YunoHost/yunohost/commit/50f3291ea7eebb7edc229229f45ce4a5cf66fd1c#diff-a1f538c2eae234f6ba78e0bed0ae54db - https://github.com/YunoHost/yunohost/commit/540291a7e072dde610d80e45ec70496d9e2e58e6#diff-a1f538c2eae234f6ba78e0bed0ae54db --- data/helpers.d/getopts | 55 ++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index 6d600e207..efaa8d065 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -53,33 +53,33 @@ ynh_handle_getopts_args () { # For each option in the array, reduce to short options for getopts (e.g. for [u]=user, --user will be -u) # And built parameters string for getopts - # ${!args_array[@]} is the list of all keys in the array (A key is 'u' in [u]=user, user is a value) + # ${!args_array[@]} is the list of all option_flags in the array (An option_flag is 'u' in [u]=user, user is a value) local getopts_parameters="" - local key="" - for key in "${!args_array[@]}" + local option_flag="" + for option_flag in "${!args_array[@]}" do - # Concatenate each keys of the array to build the string of arguments for getopts + # Concatenate each option_flags of the array to build the string of arguments for getopts # Will looks like 'abcd' for -a -b -c -d - # If the value of a key finish by =, it's an option with additionnal values. (e.g. --user bob or -u bob) - # Check the last character of the value associate to the key - if [ "${args_array[$key]: -1}" = "=" ] + # If the value of an option_flag finish by =, it's an option with additionnal values. (e.g. --user bob or -u bob) + # Check the last character of the value associate to the option_flag + if [ "${args_array[$option_flag]: -1}" = "=" ] then # For an option with additionnal values, add a ':' after the letter for getopts. - getopts_parameters="${getopts_parameters}${key}:" + getopts_parameters="${getopts_parameters}${option_flag}:" else - getopts_parameters="${getopts_parameters}${key}" + getopts_parameters="${getopts_parameters}${option_flag}" fi # Check each argument given to the function local arg="" # ${#arguments[@]} is the size of the array for arg in `seq 0 $(( ${#arguments[@]} - 1 ))` do - # And replace long option (value of the key) by the short option, the key itself + # And replace long option (value of the option_flag) by the short option, the option_flag itself # (e.g. for [u]=user, --user will be -u) # Replace long option with = - arguments[arg]="${arguments[arg]//--${args_array[$key]}/-${key} }" + arguments[arg]="${arguments[arg]//--${args_array[$option_flag]}/-${option_flag} }" # And long option without = - arguments[arg]="${arguments[arg]//--${args_array[$key]%=}/-${key}}" + arguments[arg]="${arguments[arg]//--${args_array[$option_flag]%=}/-${option_flag}}" done done @@ -98,10 +98,10 @@ ynh_handle_getopts_args () { if [ "$parameter" = "?" ] then - ynh_die "Invalid argument: -${OPTARG:-}" + ynh_die --message="Invalid argument: -${OPTARG:-}" elif [ "$parameter" = ":" ] then - ynh_die "-$OPTARG parameter requires an argument." + ynh_die --message="-$OPTARG parameter requires an argument." else local shift_value=1 # Use the long option, corresponding to the short option read by getopts, as a variable @@ -132,10 +132,11 @@ ynh_handle_getopts_args () { # Declare the content of option_var as a variable. eval ${option_var}="" # Then read the array value per value + local i for i in `seq 0 $(( ${#all_args[@]} - 1 ))` do # If this argument is an option, end here. - if [ "${all_args[$i]:0:1}" == "-" ] || [ -z "${all_args[$i]}" ] + if [ "${all_args[$i]:0:1}" == "-" ] then # Ignore the first value of the array, which is the option itself if [ "$i" -ne 0 ]; then @@ -165,25 +166,33 @@ ynh_handle_getopts_args () { # Check if there's getopts arguments if [ "${arguments[0]:0:1}" != "-" ] then - # If not, enter in legacy mode and manage the arguments as positionnal ones. - echo "! Helper used in legacy mode !" + # If not, enter in legacy mode and manage the arguments as positionnal ones.. + # Dot not echo, to prevent to go through a helper output. But print only in the log. + set -x; echo "! Helper used in legacy mode !" > /dev/null; set +x + local i for i in `seq 0 $(( ${#arguments[@]} -1 ))` do - # Use getopts_parameters as a list of key of the array args_array + # Try to use legacy_args as a list of option_flag of the array args_array + # Otherwise, fallback to getopts_parameters to get the option_flag. But an associative arrays isn't always sorted in the correct order... # Remove all ':' in getopts_parameters - getopts_parameters=${getopts_parameters//:} - # Get the key from getopts_parameters, by using the key according to the position of the argument. - key=${getopts_parameters:$i:1} - # Use the long option, corresponding to the key, as a variable + getopts_parameters=${legacy_args:-${getopts_parameters//:}} + # Get the option_flag from getopts_parameters, by using the option_flag according to the position of the argument. + option_flag=${getopts_parameters:$i:1} + if [ -z "$option_flag" ]; then + ynh_print_warn --message="Too many arguments ! \"${arguments[$i]}\" will be ignored." + continue + fi + # Use the long option, corresponding to the option_flag, as a variable # (e.g. for [u]=user, 'user' will be used as a variable) # Also, remove '=' at the end of the long option # The variable name will be stored in 'option_var' - local option_var="${args_array[$key]%=}" + local option_var="${args_array[$option_flag]%=}" # Store each value given as argument in the corresponding variable # The values will be stored in the same order than $args_array eval ${option_var}+=\"${arguments[$i]}\" done + unset legacy_args else # END LEGACY MODE # Call parse_arg and pass the modified list of args as an array of arguments. From a06ace4aa842f1c71408ce6d1803f1e6c22c8f1a Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 27 Jan 2019 23:46:26 +0100 Subject: [PATCH 145/721] [fix] Fix home_dir unset in ynh_system_user_create --- data/helpers.d/user | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index 543be1685..7f914bbde 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -61,7 +61,8 @@ ynh_system_user_create () { local use_shell # Manage arguments with getopts ynh_handle_getopts_args "$@" - local use_shell="${use_shell:-0}" + use_shell="${use_shell:-0}" + home_dir="${home_dir:-}" if ! ynh_system_user_exists "$username" # Check if the user exists on the system then # If the user doesn't exist From 63d62b4f7a77ab780d43a320b5d4c1f94b36d459 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 28 Jan 2019 00:42:56 +0100 Subject: [PATCH 146/721] [enh] Allow to dig deeper into an archive with ynh_setup_source --- data/helpers.d/utils | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 43dabfcd4..699440edb 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -118,6 +118,8 @@ ynh_backup_before_upgrade () { # SOURCE_FORMAT=tar.gz # # (Optional) Put false if sources are directly in the archive root # # default: true +# # Instead of true, SOURCE_IN_SUBDIR could be the number of sub directories +# # to remove. # SOURCE_IN_SUBDIR=false # # (Optionnal) Name of the local archive (offline setup support) # # default: ${src_id}.${src_format} @@ -136,6 +138,8 @@ ynh_backup_before_upgrade () { # If it's ok, the source archive will be uncompressed in $dest_dir. If the # SOURCE_IN_SUBDIR is true, the first level directory of the archive will be # removed. +# If SOURCE_IN_SUBDIR is a numeric value, 2 for example, the 2 first level +# directories will be removed # # Finally, patches named sources/patches/${src_id}-*.patch and extra files in # sources/extra_files/$src_id will be applied to dest_dir @@ -182,7 +186,7 @@ ynh_setup_source () { # Extract source into the app dir mkdir -p "$dest_dir" - + if ! "$src_extract" then mv $src_filename $dest_dir @@ -200,8 +204,14 @@ ynh_setup_source () { fi else local strip="" - if $src_in_subdir ; then - strip="--strip-components 1" + if [ "$src_in_subdir" != "false" ] + then + if [ "$src_in_subdir" == "true" ]; then + local sub_dirs=1 + else + local sub_dirs="$src_in_subdir" + fi + strip="--strip-components $sub_dirs" fi if [[ "$src_format" =~ ^tar.gz|tar.bz2|tar.xz$ ]] ; then tar -xf $src_filename -C "$dest_dir" $strip From 0e96a8f659964392aa52e41b078fdba7487135d0 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 28 Jan 2019 01:41:40 +0100 Subject: [PATCH 147/721] Getopts's not yet implemented for other helpers. --- data/helpers.d/getopts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index efaa8d065..6ef8e6932 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -98,10 +98,10 @@ ynh_handle_getopts_args () { if [ "$parameter" = "?" ] then - ynh_die --message="Invalid argument: -${OPTARG:-}" + ynh_die "Invalid argument: -${OPTARG:-}" elif [ "$parameter" = ":" ] then - ynh_die --message="-$OPTARG parameter requires an argument." + ynh_die "-$OPTARG parameter requires an argument." else local shift_value=1 # Use the long option, corresponding to the short option read by getopts, as a variable @@ -179,7 +179,7 @@ ynh_handle_getopts_args () { # Get the option_flag from getopts_parameters, by using the option_flag according to the position of the argument. option_flag=${getopts_parameters:$i:1} if [ -z "$option_flag" ]; then - ynh_print_warn --message="Too many arguments ! \"${arguments[$i]}\" will be ignored." + ynh_print_warn "Too many arguments ! \"${arguments[$i]}\" will be ignored." continue fi # Use the long option, corresponding to the option_flag, as a variable From f0ef491949235d48e242ace39fcc24017c6cb8f8 Mon Sep 17 00:00:00 2001 From: opi Date: Mon, 28 Jan 2019 10:29:28 +0100 Subject: [PATCH 148/721] [fix] Do not log migration to stretch in /tmp/. Issue #1280 --- src/yunohost/data_migrations/0003_migrate_to_stretch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0003_migrate_to_stretch.py b/src/yunohost/data_migrations/0003_migrate_to_stretch.py index ee8c09849..438393216 100644 --- a/src/yunohost/data_migrations/0003_migrate_to_stretch.py +++ b/src/yunohost/data_migrations/0003_migrate_to_stretch.py @@ -35,7 +35,7 @@ class MyMigration(Migration): def migrate(self): - self.logfile = "/tmp/{}.log".format(self.name) + self.logfile = "/var/log/yunohost/{}.log".format(self.name) self.check_assertions() From 2e1ccf2b99f9faec47f0455d73cbcc5ea62d15ad Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 28 Jan 2019 16:20:47 +0100 Subject: [PATCH 149/721] Add ynh_systemd_action helper --- data/helpers.d/system | 89 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/data/helpers.d/system b/data/helpers.d/system index 70cc57493..5fe62dcef 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -53,3 +53,92 @@ ynh_abort_if_errors () { ynh_get_debian_release () { echo $(lsb_release --codename --short) } + +# Start (or other actions) a service, print a log in case of failure and optionnaly wait until the service is completely started +# +# usage: ynh_systemd_action [-n service_name] [-a action] [ [-l "line to match"] [-p log_path] [-t timeout] [-e length] ] +# | arg: -n, --service_name= - Name of the service to reload. Default : $app +# | arg: -a, --action= - Action to perform with systemctl. Default: start +# | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. +# If not defined it don't wait until the service is completely started. +# | arg: -p, --log_path= - Log file - Path to the log file. Default : /var/log/$app/$app.log +# | arg: -t, --timeout= - Timeout - The maximum time to wait before ending the watching. Default : 300 seconds. +# | arg: -e, --length= - Length of the error log : Default : 20 +ynh_systemd_action() { + # Declare an array to define the options of this helper. + declare -Ar args_array=( [n]=service_name= [a]=action= [l]=line_match= [p]=log_path= [t]=timeout= [e]=length= ) + local service_name + local action + local line_match + local length + local log_path + local timeout + + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local service_name="${service_name:-$app}" + local action=${action:-start} + local log_path="${log_path:-/var/log/$service_name/$service_name.log}" + local length=${length:-20} + local timeout=${timeout:-300} + + # Start to read the log + if [[ -n "${line_match:-}" ]] + then + local templog="$(mktemp)" + # Following the starting of the app in its log + if [ "$log_path" == "systemd" ] ; then + # Read the systemd journal + journalctl -u $service_name -f --since=-45 > "$templog" & + else + # Read the specified log file + tail -F -n0 "$log_path" > "$templog" & + fi + # Get the PID of the tail command + local pid_tail=$! + fi + + echo "${action^} the service $service_name" >&2 + systemctl $action $service_name \ + || ( journalctl --lines=$length -u $service_name >&2 \ + ; test -n "$log_path" && echo "--" && tail --lines=$length "$log_path" >&2 \ + ; false ) + + # Start the timeout and try to find line_match + if [[ -n "${line_match:-}" ]] + then + local i=0 + for i in $(seq 1 $timeout) + do + # Read the log until the sentence is found, that means the app finished to start. Or run until the timeout + if grep --quiet "$line_match" "$templog" + then + echo "The service $service_name has correctly started." >&2 + break + fi + echo -n "." >&2 + sleep 1 + done + if [ $i -eq $timeout ] + then + echo "The service $service_name didn't fully started before the timeout." >&2 + echo "Please find here an extract of the end of the log of the service $service_name:" + journalctl --lines=$length -u $service_name >&2 + test -n "$log_path" && echo "--" && tail --lines=$length "$log_path" >&2 + fi + + echo "" + ynh_clean_check_starting + fi +} + +# Clean temporary process and file used by ynh_check_starting +# (usually used in ynh_clean_setup scripts) +# +# usage: ynh_clean_check_starting +ynh_clean_check_starting () { + # Stop the execution of tail. + kill -s 15 $pid_tail 2>&1 + ynh_secure_remove "$templog" 2>&1 +} From 35ffadbe1c7f5967a485f1282257d7244e9641f1 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 28 Jan 2019 16:26:43 +0100 Subject: [PATCH 150/721] Use ynh_systemd_action for backend helpers --- data/helpers.d/backend | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index e73644d2d..8d353365d 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -117,10 +117,10 @@ ynh_remove_systemd_config () { local finalsystemdconf="/etc/systemd/system/$service_name.service" if [ -e "$finalsystemdconf" ]; then - sudo systemctl stop $service_name - sudo systemctl disable $service_name + ynh_systemd_action --service_name=$service_name --action=stop + systemctl disable $service_name ynh_secure_remove "$finalsystemdconf" - sudo systemctl daemon-reload + systemctl daemon-reload fi } @@ -186,7 +186,7 @@ ynh_add_nginx_config () { ynh_store_file_checksum "$finalnginxconf" - sudo systemctl reload nginx + ynh_systemd_action --service_name=nginx --action=reload } # Remove the dedicated nginx config @@ -194,7 +194,7 @@ ynh_add_nginx_config () { # usage: ynh_remove_nginx_config ynh_remove_nginx_config () { ynh_secure_remove "/etc/nginx/conf.d/$domain.d/$app.conf" - sudo systemctl reload nginx + ynh_systemd_action --service_name=nginx --action=reload } # Create a dedicated php-fpm config @@ -229,7 +229,7 @@ ynh_add_fpm_config () { sudo chown root: "$finalphpini" ynh_store_file_checksum "$finalphpini" fi - sudo systemctl reload $fpm_service + ynh_systemd_action --service_name=$fpm_service --action=reload } # Remove the dedicated php-fpm config @@ -245,5 +245,5 @@ ynh_remove_fpm_config () { fi ynh_secure_remove "$fpm_config_dir/pool.d/$app.conf" ynh_secure_remove "$fpm_config_dir/conf.d/20-$app.ini" 2>&1 - sudo systemctl reload $fpm_service + ynh_systemd_action --service_name=$fpm_service --action=reload } From b9fa901bda11a97c9460ac5f426bc38a0e21c6c3 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 28 Jan 2019 16:31:54 +0100 Subject: [PATCH 151/721] Add ynh_script_progression --- data/helpers.d/print | 78 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/data/helpers.d/print b/data/helpers.d/print index 2f451bc24..1d7f6f6fa 100644 --- a/data/helpers.d/print +++ b/data/helpers.d/print @@ -124,3 +124,81 @@ ynh_print_ON () { # Print an echo only for the log, to be able to know that ynh_print_ON has been called. echo ynh_print_ON > /dev/null } + +# Print a message as INFO and show progression during an app script +# +# usage: ynh_script_progression --message=message [--weight=weight] [--time] +# | arg: -m, --message= - The text to print +# | arg: -w, --weight= - The weight for this progression. This value is 1 by default. Use a bigger value for a longer part of the script. +# | arg: -t, --time= - Print the execution time since the last call to this helper. Especially usefull to define weights. +# | arg: -l, --last= - Use for the last call of the helper, to fill te progression bar. +increment_progression=0 +previous_weight=0 +# Define base_time when the file is sourced +base_time=$(date +%s) +ynh_script_progression () { + # Declare an array to define the options of this helper. + declare -Ar args_array=( [m]=message= [w]=weight= [t]=time [l]=last ) + local message + local weight + local time + local last + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + weight=${weight:-1} + time=${time:-0} + last=${last:-0} + + # Get execution time since the last $base_time + local exec_time=$(( $(date +%s) - $base_time )) + base_time=$(date +%s) + + # Get the number of occurrences of 'ynh_script_progression' in the script. Except those are commented. + local helper_calls="$(grep --count "^[^#]*ynh_script_progression" $0)" + # Get the number of call with a weight value + local weight_calls=$(grep --perl-regexp --count "^[^#]*ynh_script_progression.*(--weight|-w )" $0) + + # Get the weight of each occurrences of 'ynh_script_progression' in the script using --weight + local weight_valuesA="$(grep --perl-regexp "^[^#]*ynh_script_progression.*--weight" $0 | sed 's/.*--weight[= ]\([[:digit:]].*\)/\1/g')" + # Get the weight of each occurrences of 'ynh_script_progression' in the script using -w + local weight_valuesB="$(grep --perl-regexp "^[^#]*ynh_script_progression.*-w " $0 | sed 's/.*-w[= ]\([[:digit:]].*\)/\1/g')" + # Each value will be on a different line. + # Remove each 'end of line' and replace it by a '+' to sum the values. + local weight_values=$(( $(echo "$weight_valuesA" | tr '\n' '+') + $(echo "$weight_valuesB" | tr '\n' '+') 0 )) + + # max_progression is a total number of calls to this helper. + # Less the number of calls with a weight value. + # Plus the total of weight values + local max_progression=$(( $helper_calls - $weight_calls + $weight_values )) + + # Increment each execution of ynh_script_progression in this script by the weight of the previous call. + increment_progression=$(( $increment_progression + $previous_weight )) + # Store the weight of the current call in $previous_weight for next call + previous_weight=$weight + + # Set the scale of the progression bar + local scale=20 + # progress_string(1,2) should have the size of the scale. + local progress_string1="####################" + local progress_string0="...................." + + # Reduce $increment_progression to the size of the scale + if [ $last -eq 0 ] + then + local effective_progression=$(( $increment_progression * $scale / $max_progression )) + # If last is specified, fill immediately the progression_bar + else + local effective_progression=$scale + fi + + # Build $progression_bar from progress_string(1,2) according to $effective_progression + local progression_bar="${progress_string1:0:$effective_progression}${progress_string0:0:$(( $scale - $effective_progression ))}" + + local print_exec_time="" + if [ $time -eq 1 ] + then + print_exec_time=" [$(date +%Hh%Mm,%Ss --date="0 + $exec_time sec")]" + fi + + ynh_print_info "[$progression_bar] > ${message}${print_exec_time}" +} From 68b01ae6ec529f04e35ae02daf518a540ab6998a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Jan 2019 01:51:52 +0100 Subject: [PATCH 152/721] Warn the user that ynh_secure_remove should be used with only one argument --- data/helpers.d/filesystem | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index dfea026b6..e8f5d349e 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -348,6 +348,11 @@ ynh_secure_remove () { /var/www \ /home/yunohost.app" + if [[ -n "$2" ]] + then + echo "/!\ Packager ! You provided a second argument to ynh_secure_remove but it will be ignored... Use this helper with one argument at time." >&2 + fi + if [[ "$forbidden_path" =~ "$path_to_remove" \ # Match all paths or subpaths in $forbidden_path || "$path_to_remove" =~ ^/[[:alnum:]]+$ \ From fdb2e770af3d46a8b0c861e457a587fa28bfdd06 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 18 Jan 2019 17:11:01 +0000 Subject: [PATCH 153/721] Added translation using Weblate (Basque) --- locales/eu.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/eu.json diff --git a/locales/eu.json b/locales/eu.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/eu.json @@ -0,0 +1 @@ +{} From 5a9a909c564b48931049c4c7252f8e9b990c8c3e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Jan 2019 16:43:56 +0100 Subject: [PATCH 154/721] Update changelog for 3.4.2 --- debian/changelog | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/debian/changelog b/debian/changelog index 48ea4b2a0..244d9d35d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,21 @@ +yunohost (3.4.2) stable; urgency=low + + - [fix] Do not log stretch migration in /tmp/ (#632) + - [fix] Some issues with ynh_handle_getopts_args (#628) + - [fix] Revert some stuff about separates php-ini file (c.f. #548) (#627) + - [fix] App conflicted with itself during change_url (#626) + - [fix] Improve `ynh_package_install_from_equivs` debuggability (#625) + - [enh] Add systemd log handling (#624) + - [enh] Update spectre meltdown checker (#620) + - [fix] Propagate HTTP2, more_set_headers and ecdh_curve changes to webadmin (#618) + - [enh] Control the login shell when creating users in ynh_system_user_create (#455, #629) + - [fix] Postgresql-9.4 was being detected as installed whereas it was in fact not (969577b) + - [fix] Restoring system failed because of temporary dumb password being refused (51712f9) + + Thanks to all contributors (Aleks, frju365, JimboJoe, kay0u, Maniack, opi) ! <3 + + -- Alexandre Aubin Tue, 29 Jan 2019 16:42:00 +0000 + yunohost (3.4.1) testing; urgency=low * [fix] `_run_service_command` not properly returning False if command fails (#616) From a26994fd70d9f8b58027548b9579e9818ed085af Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Jan 2019 23:04:14 +0100 Subject: [PATCH 155/721] Update DNS resolver list according to diyisp.org --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index bc36ef365..f2bb846f7 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -14,15 +14,17 @@ nameserver 80.67.169.40 nameserver 80.67.188.188 # (FR) ARN nameserver 89.234.141.66 +# (FR) Aquilenet +nameserver 185.233.100.100 +nameserver 185.233.100.101 # (FR) gozmail / grifon nameserver 89.234.186.18 # (DE) FoeBud / Digital Courage nameserver 85.214.20.141 -# (FR) Aquilenet [added manually, following comments from @sachaz] -nameserver 141.255.128.100 -nameserver 141.255.128.101 -# (DE) CCC Berlin -nameserver 213.73.91.35 +# (DE) CCC Berlin (not working since Dec 2018 ?) +#nameserver 213.73.91.35 +# (DE) AS250 +nameserver 194.150.168.168 # (DE) Ideal-Hosting nameserver 84.200.69.80 nameserver 84.200.70.40 From 8b3e645578601eb3ad1d774748a0498578e7c937 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Jan 2019 14:02:45 +0100 Subject: [PATCH 156/721] [microdecision] Archive folder might not exist --- .../data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py index 0abb18a26..f53074a89 100644 --- a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py +++ b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py @@ -39,7 +39,8 @@ class MyMigration(Migration): # Update local archives folder permissions, so that # admin can scp archives out of the server - chown(ARCHIVES_PATH, uid="admin", gid="root") + if os.path.isdir(ARCHIVES_PATH): + chown(ARCHIVES_PATH, uid="admin", gid="root") def backward(self): From d5bf9a61b87b2b1b28c5881d76ebbb7fd9cecfd6 Mon Sep 17 00:00:00 2001 From: opi Date: Wed, 30 Jan 2019 14:09:59 +0100 Subject: [PATCH 157/721] [fix] Remove old SMTP port (465) from Fail2ban jail.conf. Fix #1283 --- data/templates/fail2ban/jail.conf | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/data/templates/fail2ban/jail.conf b/data/templates/fail2ban/jail.conf index 05eb7e7a8..9b4d39f17 100644 --- a/data/templates/fail2ban/jail.conf +++ b/data/templates/fail2ban/jail.conf @@ -513,27 +513,27 @@ logpath = %(vsftpd_log)s # ASSP SMTP Proxy Jail [assp] -port = smtp,465,submission +port = smtp,submission logpath = /root/path/to/assp/logs/maillog.txt [courier-smtp] -port = smtp,465,submission +port = smtp,submission logpath = %(syslog_mail)s backend = %(syslog_backend)s [postfix] -port = smtp,465,submission +port = smtp,submission logpath = %(postfix_log)s backend = %(postfix_backend)s [postfix-rbl] -port = smtp,465,submission +port = smtp,submission logpath = %(postfix_log)s backend = %(postfix_backend)s maxretry = 1 @@ -541,14 +541,14 @@ maxretry = 1 [sendmail-auth] -port = submission,465,smtp +port = submission,smtp logpath = %(syslog_mail)s backend = %(syslog_backend)s [sendmail-reject] -port = smtp,465,submission +port = smtp,submission logpath = %(syslog_mail)s backend = %(syslog_backend)s @@ -556,7 +556,7 @@ backend = %(syslog_backend)s [qmail-rbl] filter = qmail -port = smtp,465,submission +port = smtp,submission logpath = /service/qmail/log/main/current @@ -564,14 +564,14 @@ logpath = /service/qmail/log/main/current # but can be set by syslog_facility in the dovecot configuration. [dovecot] -port = pop3,pop3s,imap,imaps,submission,465,sieve +port = pop3,pop3s,imap,imaps,submission,sieve logpath = %(dovecot_log)s backend = %(dovecot_backend)s [sieve] -port = smtp,465,submission +port = smtp,submission logpath = %(dovecot_log)s backend = %(dovecot_backend)s @@ -584,19 +584,19 @@ logpath = %(solidpop3d_log)s [exim] -port = smtp,465,submission +port = smtp,submission logpath = %(exim_main_log)s [exim-spam] -port = smtp,465,submission +port = smtp,submission logpath = %(exim_main_log)s [kerio] -port = imap,smtp,imaps,465 +port = imap,smtp,imaps logpath = /opt/kerio/mailserver/store/logs/security.log @@ -607,14 +607,14 @@ logpath = /opt/kerio/mailserver/store/logs/security.log [courier-auth] -port = smtp,465,submission,imaps,pop3,pop3s +port = smtp,submission,imaps,pop3,pop3s logpath = %(syslog_mail)s backend = %(syslog_backend)s [postfix-sasl] -port = smtp,465,submission,imap,imaps,pop3,pop3s +port = smtp,submission,imap,imaps,pop3,pop3s # 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. @@ -631,7 +631,7 @@ backend = %(syslog_backend)s [squirrelmail] -port = smtp,465,submission,imap,imap2,imaps,pop3,pop3s,http,https,socks +port = smtp,submission,imap,imap2,imaps,pop3,pop3s,http,https,socks logpath = /var/lib/squirrelmail/prefs/squirrelmail_access_log From 40382acbfbf9677eac675d9457109a03368e3ef6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Jan 2019 15:22:32 +0100 Subject: [PATCH 158/721] Update gozmail IP --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index f2bb846f7..c7cd29d2c 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -18,7 +18,7 @@ nameserver 89.234.141.66 nameserver 185.233.100.100 nameserver 185.233.100.101 # (FR) gozmail / grifon -nameserver 89.234.186.18 +nameserver 80.67.190.200 # (DE) FoeBud / Digital Courage nameserver 85.214.20.141 # (DE) CCC Berlin (not working since Dec 2018 ?) From 4e7a4f68273087184f311f19d9dcfc3fbe153add Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Jan 2019 15:25:02 +0100 Subject: [PATCH 159/721] Update IP for CCC Berlin --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index c7cd29d2c..7eed1142f 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -21,8 +21,8 @@ nameserver 185.233.100.101 nameserver 80.67.190.200 # (DE) FoeBud / Digital Courage nameserver 85.214.20.141 -# (DE) CCC Berlin (not working since Dec 2018 ?) -#nameserver 213.73.91.35 +# (DE) CCC Berlin +nameserver 195.160.173.53 # (DE) AS250 nameserver 194.150.168.168 # (DE) Ideal-Hosting From 4a7e33a1453a387b665372ead85168e976ef2f72 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Jan 2019 17:34:51 +0100 Subject: [PATCH 160/721] #564 broke the autoconfig, nginx was lookin for mailconfig.xml instead of main/config.xml --- data/templates/nginx/server.tpl.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index ee20c29c9..0c221f188 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -12,7 +12,7 @@ server { } location /.well-known/autoconfig/mail/ { - alias /var/www/.well-known/{{ domain }}/autoconfig/mail; + alias /var/www/.well-known/{{ domain }}/autoconfig/mail/; } access_log /var/log/nginx/{{ domain }}-access.log; From d95931c8f139b370b4ec4b8fb6e8871153fc18ec Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Jan 2019 19:23:36 +0100 Subject: [PATCH 161/721] Ignore stderr from meltdown checker --- src/yunohost/tools.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 92ed086bf..915b63940 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -725,9 +725,14 @@ def _check_if_vulnerable_to_meltdown(): call = subprocess.Popen("bash %s --batch json --variant 3" % SCRIPT_PATH, shell=True, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) + stderr=subprocess.PIPE) - output, _ = call.communicate() + # TODO / FIXME : here we are ignoring error messages ... + # in particular on RPi2 and other hardware, the script complains about + # "missing some kernel info (see -v), accuracy might be reduced" + # Dunno what to do about that but we probably don't want to harass + # users with this warning ... + output, err = call.communicate() assert call.returncode in (0, 2, 3), "Return code: %s" % call.returncode CVEs = json.loads(output) From 73ab2785f2b04eeb173e869e93c80872f4b412db Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Jan 2019 19:38:39 +0100 Subject: [PATCH 162/721] Update changelog for 3.4.2.1 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 244d9d35d..ba4c44e56 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +yunohost (3.4.2.1) stable; urgency=low + + Small issues + - Fix parsing of the Meltdown vulnerability checker (ignore stderr :/) + - Mail autoconfig was broken, follow-up of #564 + - Handle the fact that the archive folder might not exist, in migration 0008 + + -- Alexandre Aubin Wed, 30 Jan 2019 16:37:00 +0000 + yunohost (3.4.2) stable; urgency=low - [fix] Do not log stretch migration in /tmp/ (#632) From d8ef303cded9679450ed12e1c37b84dbcc3d5765 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Jan 2019 21:16:56 +0100 Subject: [PATCH 163/721] Missing os import -.- --- .../data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py index f53074a89..0976f1354 100644 --- a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py +++ b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py @@ -1,3 +1,4 @@ +import os import re from moulinette import m18n From 0a0ab3cf9fb304e3733cc5bdb52739bf95f3a547 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Jan 2019 21:18:09 +0100 Subject: [PATCH 164/721] Update changelog for 3.4.2.2 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index ba4c44e56..6f930aed9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.4.2.2) stable; urgency=low + + - Silly bug in migraton 8 :| + + -- Alexandre Aubin Wed, 30 Jan 2019 21:17:00 +0000 + yunohost (3.4.2.1) stable; urgency=low Small issues From d77bc92b6e4be77e30b2600ea099c1805f4a39fb Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Thu, 31 Jan 2019 17:54:25 +0100 Subject: [PATCH 165/721] Check if dpkg is broken --- data/helpers.d/package | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/data/helpers.d/package b/data/helpers.d/package index 2cbca4840..6bd655fe0 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -15,6 +15,21 @@ ynh_wait_dpkg_free() { # Sleep an exponential time at each round sleep $(( try * try )) else + # Check if dpkg hasn't been interrupted and is fully available. + # See this for more information: https://sources.debian.org/src/apt/1.4.9/apt-pkg/deb/debsystem.cc/#L141-L174 + local dpkg_dir="/var/lib/dpkg/updates/" + + # For each file in $dpkg_dir + while read dpkg_file <&9 + do + # Check if the name of this file contains only numbers. + if echo "$dpkg_file" | grep -Pq "^[[:digit:]]*$" + then + # If so, that a remaining of dpkg. + ynh_print_err "E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem." + return 1 + fi + done 9<<< "$(ls -1 $dpkg_dir)" return 0 fi done From db83dc089334ccf0314da5d9c06cb82483d90545 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 3 Feb 2019 03:20:19 +0100 Subject: [PATCH 166/721] [wip] Fix password in log --- bin/yunohost-api | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/yunohost-api b/bin/yunohost-api index 93d44c256..6e47df7ef 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -149,6 +149,11 @@ def _init_moulinette(use_websocket=True, debug=False, verbose=False): 'handlers': [], 'propagate': True, }, + 'gnupg': { + 'level': 'INFO', + 'handlers': [], + 'propagate': False, + }, }, 'root': { 'level': level, From 66ef3e208bbac0472ae583c6ef4e18013e892b64 Mon Sep 17 00:00:00 2001 From: Taekiro Date: Sun, 3 Feb 2019 09:54:48 +0100 Subject: [PATCH 167/721] Add IPv6 nameserver to resolv.dnsmasq.conf --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index 7eed1142f..197ee2d64 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -9,25 +9,37 @@ # (FR) FDN nameserver 80.67.169.12 +nameserver 2001:910:800::12 nameserver 80.67.169.40 +nameserver 2001:910:800::40 # (FR) LDN nameserver 80.67.188.188 +nameserver 2001:913::8 # (FR) ARN nameserver 89.234.141.66 +nameserver 2a00:5881:8100:1000::3 # (FR) Aquilenet nameserver 185.233.100.100 +nameserver 2a0c:e300::100 nameserver 185.233.100.101 +nameserver 2a0c:e300::101 # (FR) gozmail / grifon nameserver 80.67.190.200 +nameserver 2a00:5884:8218::1 # (DE) FoeBud / Digital Courage nameserver 85.214.20.141 # (DE) CCC Berlin nameserver 195.160.173.53 # (DE) AS250 nameserver 194.150.168.168 +nameserver 2001:4ce8::53 # (DE) Ideal-Hosting nameserver 84.200.69.80 +nameserver 2001:1608:10:25::1c04:b12f nameserver 84.200.70.40 +nameserver 2001:1608:10:25::9249:d69b # (DK) censurfridns nameserver 91.239.100.100 +nameserver 2001:67c:28a4:: nameserver 89.233.43.71 +nameserver 2002:d596:2a92:1:71:53:: From 81bbb62b8213b678d37fdbe19a10748d8d143a93 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Feb 2019 18:27:50 +0100 Subject: [PATCH 168/721] [microdecision] Missing translation for key 'file_not_exist' --- locales/en.json | 1 + src/yunohost/hook.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 7292f56e2..099cef21b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -186,6 +186,7 @@ "extracting": "Extracting…", "experimental_feature": "Warning: this feature is experimental and not consider stable, you shouldn't be using it except if you know what you are doing.", "field_invalid": "Invalid field '{:s}'", + "file_does_not_exist": "The file {path:s} does not exists.", "firewall_reload_failed": "Unable to reload the firewall", "firewall_reloaded": "The firewall has been reloaded", "firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log.", diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 79e9289ef..ca93c7f03 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -317,7 +317,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, if path[0] != '/': path = os.path.realpath(path) if not os.path.isfile(path): - raise YunohostError('file_not_exist', path=path) + raise YunohostError('file_does_not_exist', path=path) # Construct command variables cmd_args = '' From c849b4732a6ab5f948a7eb8742c3e1503b450a45 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Feb 2019 19:03:47 +0100 Subject: [PATCH 169/721] [microdecision] Move this message to info to improve UX --- locales/en.json | 2 +- src/yunohost/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 099cef21b..8528c2576 100644 --- a/locales/en.json +++ b/locales/en.json @@ -469,7 +469,7 @@ "unlimit": "No quota", "unrestore_app": "App '{app:s}' will not be restored", "update_cache_failed": "Unable to update APT cache", - "updating_apt_cache": "Updating the list of available packages…", + "updating_apt_cache": "Fetching available upgrades for system packages…", "upgrade_complete": "Upgrade complete", "upgrading_packages": "Upgrading packages…", "upnp_dev_not_found": "No UPnP device found", diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 915b63940..a220e21ca 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -472,7 +472,7 @@ def tools_update(ignore_apps=False, ignore_packages=False): cache = apt.Cache() # Update APT cache - logger.debug(m18n.n('updating_apt_cache')) + logger.info(m18n.n('updating_apt_cache')) if not cache.update(): raise YunohostError('update_cache_failed') From 8e1034771af5ecc9acce6e9966ac077ec2ea36c7 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Mon, 4 Feb 2019 23:01:16 +0100 Subject: [PATCH 170/721] use setting security_ciphers_compatibility to define security configurations --- data/hooks/conf_regen/03-ssh | 8 +++++++- data/hooks/conf_regen/15-nginx | 6 ++++++ data/templates/nginx/server.tpl.conf | 20 ++++++++++++-------- data/templates/ssh/sshd_config | 15 +++++++++++---- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index 9de527518..330166f08 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -12,7 +12,7 @@ do_pre_regen() { [[ ! -f /etc/yunohost/from_script ]] || return 0 cd /usr/share/yunohost/templates/ssh - + # do not listen to IPv6 if unavailable [[ -f /proc/net/if_inet6 ]] && ipv6_enabled=true || ipv6_enabled=false @@ -23,8 +23,14 @@ do_pre_regen() { ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null || true)" fi + # Support different strategy for security configurations + if [[ -n "$(yunohost settings get 'security.ciphers.compatibility')" ]]; then + security_ciphers_compatibility="$(yunohost settings get 'security.ciphers.compatibility')" + fi + export ssh_keys export ipv6_enabled + export security_ciphers_compatibility ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config" } diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 461c10c0c..97543dcfa 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -36,6 +36,11 @@ do_pre_regen() { main_domain=$(cat /etc/yunohost/current_host) domain_list=$(sudo yunohost domain list --output-as plain --quiet) + # Support different strategy for security configurations + if [[ -n "$(yunohost settings get 'security.ciphers.compatibility')" ]]; then + security_ciphers_compatibility="$(yunohost settings get 'security.ciphers.compatibility')" + fi + # add domain conf files for domain in $domain_list; do domain_conf_dir="${nginx_conf_dir}/${domain}.d" @@ -44,6 +49,7 @@ do_pre_regen() { mkdir -p "$mail_autoconfig_dir" # NGINX server configuration + export security_ciphers_compatibility export domain export domain_cert_ca=$(yunohost domain cert-status $domain --json \ | jq ".certificates.\"$domain\".CA_type" \ diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 0c221f188..50ee1b9b8 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -29,6 +29,15 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; + {%- if security_ciphers_compatibility == "modern" -%} + # Ciphers with modern compatibility + # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern + # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) + ssl_protocols TLSv1.2; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_prefer_server_ciphers on; + + {%- else -%} # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 ssl_ecdh_curve secp521r1:secp384r1:prime256v1; ssl_prefer_server_ciphers on; @@ -38,20 +47,15 @@ server { ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; - # Ciphers with modern compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern - # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) - #ssl_protocols TLSv1.2; - #ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; - # Uncomment the following directive after DH generation # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; + {%- endif -%} # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners # https://wiki.mozilla.org/Security/Guidelines/Web_Security - # https://observatory.mozilla.org/ - more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; + # https://observatory.mozilla.org/ + more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'"; more_set_headers "X-Content-Type-Options : nosniff"; diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index ed870e5dc..7194a309d 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -15,10 +15,17 @@ HostKey {{ key }}{% endfor %} # https://infosec.mozilla.org/guidelines/openssh # ############################################## -# Keys, ciphers and MACS -KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256 -Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com +{%- if security_ciphers_compatibility == "intermediate" -%} + KexAlgorithms diffie-hellman-group-exchange-sha256 + Ciphers aes256-ctr,aes192-ctr,aes128-ctr + MACs hmac-sha2-512,hmac-sha2-256 +{%- else -%} + # By default use "modern" Mozilla configuration + # Keys, ciphers and MACS + KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256 + Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr + MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com +{%- endif -%} # Use kernel sandbox mechanisms where possible in unprivileged processes UsePrivilegeSeparation sandbox From 1895e1ac6339c1e1a4547c509108c606a88a4ce1 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Wed, 6 Feb 2019 11:18:16 +0100 Subject: [PATCH 171/721] since 0 instead of since 45 --- data/helpers.d/system | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index 5fe62dcef..25d135401 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -90,7 +90,7 @@ ynh_systemd_action() { # Following the starting of the app in its log if [ "$log_path" == "systemd" ] ; then # Read the systemd journal - journalctl -u $service_name -f --since=-45 > "$templog" & + journalctl --unit=$service_name --follow --since=-0 --quiet > "$templog" & else # Read the specified log file tail -F -n0 "$log_path" > "$templog" & From 0267a798a75976ccfbc175c32d0f58905bc89034 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 6 Feb 2019 19:27:43 +0100 Subject: [PATCH 172/721] [fix] sterror doesn't exist most of the time --- src/yunohost/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 6f9402f83..0f73aea43 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1173,7 +1173,7 @@ def app_makedefault(operation_logger, auth, app, domain=None): with open('/etc/ssowat/conf.json.persistent') as json_conf: ssowat_conf = json.loads(str(json_conf.read())) except ValueError as e: - raise YunohostError('ssowat_persistent_conf_read_error', error=e.strerror) + raise YunohostError('ssowat_persistent_conf_read_error', error=e) except IOError: ssowat_conf = {} @@ -1186,7 +1186,7 @@ def app_makedefault(operation_logger, auth, app, domain=None): with open('/etc/ssowat/conf.json.persistent', 'w+') as f: json.dump(ssowat_conf, f, sort_keys=True, indent=4) except IOError as e: - raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror) + raise YunohostError('ssowat_persistent_conf_write_error', error=e) os.system('chmod 644 /etc/ssowat/conf.json.persistent') @@ -1824,7 +1824,7 @@ def _extract_app_from_file(path, remove=False): except IOError: raise YunohostError('app_install_files_invalid') except ValueError as e: - raise YunohostError('app_manifest_invalid', error=e.strerror) + raise YunohostError('app_manifest_invalid', error=e) logger.debug(m18n.n('done')) @@ -1917,7 +1917,7 @@ def _fetch_app_from_git(app): except subprocess.CalledProcessError: raise YunohostError('app_sources_fetch_failed') except ValueError as e: - raise YunohostError('app_manifest_invalid', error=e.strerror) + raise YunohostError('app_manifest_invalid', error=e) else: logger.debug(m18n.n('done')) @@ -1970,7 +1970,7 @@ def _fetch_app_from_git(app): except subprocess.CalledProcessError: raise YunohostError('app_sources_fetch_failed') except ValueError as e: - raise YunohostError('app_manifest_invalid', error=e.strerror) + raise YunohostError('app_manifest_invalid', error=e) else: logger.debug(m18n.n('done')) @@ -2250,7 +2250,7 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None): try: user_info(auth, arg_value) except YunohostError as e: - raise YunohostError('app_argument_invalid', name=arg_name, error=e.strerror) + raise YunohostError('app_argument_invalid', name=arg_name, error=e) elif arg_type == 'app': if not _is_installed(arg_value): raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('app_unknown')) From 169ae96f0a52129e505972061709af0e2a8ec0ad Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 6 Feb 2019 19:32:35 +0100 Subject: [PATCH 173/721] [mod] please flake8 by respecting pep8 and removing except: --- src/yunohost/app.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0f73aea43..0bca68787 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -734,8 +734,8 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on if answer.upper() != "Y": raise YunohostError("aborting") - raw_app_list = app_list(raw=True) + if app in raw_app_list or ('@' in app) or ('http://' in app) or ('https://' in app): if app in raw_app_list: state = raw_app_list[app].get("state", "notworking") @@ -1209,8 +1209,8 @@ def app_setting(app, key, value=None, delete=False): if value is None and not delete: try: return app_settings[key] - except: - logger.debug("cannot get app setting '%s' for '%s'", key, app) + except Exception as e: + logger.debug("cannot get app setting '%s' for '%s' (%s)", key, app, e) return None else: if delete and key in app_settings: @@ -1394,7 +1394,8 @@ def app_ssowatconf(auth): try: apps_list = app_list(installed=True)['apps'] - except: + except Exception as e: + logger.debug("cannot get installed app list because %s", e) apps_list = [] def _get_setting(settings, name): @@ -1907,7 +1908,7 @@ def _fetch_app_from_git(app): # we will be able to use it. Without this option all the history # of the submodules repo is downloaded. subprocess.check_call([ - 'git', 'clone', '-b', branch, '--single-branch', '--recursive', '--depth=1', url, + 'git', 'clone', '-b', branch, '--single-branch', '--recursive', '--depth=1', url, extracted_app_folder]) subprocess.check_call([ 'git', 'reset', '--hard', branch @@ -1925,8 +1926,8 @@ def _fetch_app_from_git(app): manifest['remote'] = {'type': 'git', 'url': url, 'branch': branch} try: revision = _get_git_last_commit_hash(url, branch) - except: - pass + except Exception as e: + logger.debug("cannot get last commit hash because: %s ", e) else: manifest['remote']['revision'] = revision else: From 060370ef889829d6bacf08f62bd7a7b5ad53177d Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Wed, 6 Feb 2019 22:46:21 +0100 Subject: [PATCH 174/721] Fix #635 on ynh_secure_remove --- data/helpers.d/filesystem | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 6e0edfa37..88d51750b 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -392,10 +392,10 @@ ynh_secure_remove () { /var/www \ /home/yunohost.app" - if [[ -n "$2" ]] - then - echo "/!\ Packager ! You provided a second argument to ynh_secure_remove but it will be ignored... Use this helper with one argument at time." >&2 - fi + if [ $# -ge 2 ] + then + echo "/!\ Packager ! You provided more than one argument to ynh_secure_remove but it will be ignored... Use this helper with one argument at time." >&2 + fi if [[ "$forbidden_path" =~ "$file" \ # Match all paths or subpaths in $forbidden_path From f0e65750623e0a6646441ba212ffc12c5388b3e3 Mon Sep 17 00:00:00 2001 From: ljf Date: Sun, 3 Feb 2019 03:20:19 +0100 Subject: [PATCH 175/721] [wip] Fix password in log --- bin/yunohost-api | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/yunohost-api b/bin/yunohost-api index 93d44c256..6e47df7ef 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -149,6 +149,11 @@ def _init_moulinette(use_websocket=True, debug=False, verbose=False): 'handlers': [], 'propagate': True, }, + 'gnupg': { + 'level': 'INFO', + 'handlers': [], + 'propagate': False, + }, }, 'root': { 'level': level, From 0b98df85f1f75766bbf72cf9cbc266c50f9536a5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 29 Jan 2019 23:04:14 +0100 Subject: [PATCH 176/721] Update DNS resolver list according to diyisp.org --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index bc36ef365..f2bb846f7 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -14,15 +14,17 @@ nameserver 80.67.169.40 nameserver 80.67.188.188 # (FR) ARN nameserver 89.234.141.66 +# (FR) Aquilenet +nameserver 185.233.100.100 +nameserver 185.233.100.101 # (FR) gozmail / grifon nameserver 89.234.186.18 # (DE) FoeBud / Digital Courage nameserver 85.214.20.141 -# (FR) Aquilenet [added manually, following comments from @sachaz] -nameserver 141.255.128.100 -nameserver 141.255.128.101 -# (DE) CCC Berlin -nameserver 213.73.91.35 +# (DE) CCC Berlin (not working since Dec 2018 ?) +#nameserver 213.73.91.35 +# (DE) AS250 +nameserver 194.150.168.168 # (DE) Ideal-Hosting nameserver 84.200.69.80 nameserver 84.200.70.40 From f050db0fbf7efcaf85b9e5b1d6c7b1f834505398 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Jan 2019 15:22:32 +0100 Subject: [PATCH 177/721] Update gozmail IP --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index f2bb846f7..c7cd29d2c 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -18,7 +18,7 @@ nameserver 89.234.141.66 nameserver 185.233.100.100 nameserver 185.233.100.101 # (FR) gozmail / grifon -nameserver 89.234.186.18 +nameserver 80.67.190.200 # (DE) FoeBud / Digital Courage nameserver 85.214.20.141 # (DE) CCC Berlin (not working since Dec 2018 ?) From ed481e05d832022e13074f7478d03477685d84c6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 30 Jan 2019 15:25:02 +0100 Subject: [PATCH 178/721] Update IP for CCC Berlin --- data/templates/dnsmasq/plain/resolv.dnsmasq.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf index c7cd29d2c..7eed1142f 100644 --- a/data/templates/dnsmasq/plain/resolv.dnsmasq.conf +++ b/data/templates/dnsmasq/plain/resolv.dnsmasq.conf @@ -21,8 +21,8 @@ nameserver 185.233.100.101 nameserver 80.67.190.200 # (DE) FoeBud / Digital Courage nameserver 85.214.20.141 -# (DE) CCC Berlin (not working since Dec 2018 ?) -#nameserver 213.73.91.35 +# (DE) CCC Berlin +nameserver 195.160.173.53 # (DE) AS250 nameserver 194.150.168.168 # (DE) Ideal-Hosting From 45d2001692256c9eec59f3b65804d624ba985f0e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Feb 2019 15:20:50 +0100 Subject: [PATCH 179/721] Update changelog for 3.4.2.3 --- debian/changelog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/changelog b/debian/changelog index 6f930aed9..3f3a6a5ef 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +yunohost (3.4.2.3) stable; urgency=low + + - [fix] Admin password appearing in logs after logging in on webadmin + - [fix] Update friendly DNS resolver list + + -- Alexandre Aubin Thu, 07 Feb 2019 03:20:10 +0000 + yunohost (3.4.2.2) stable; urgency=low - Silly bug in migraton 8 :| From 8e8ed223951d56c9f115efbe008b6b110a0ec4a7 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 9 Feb 2019 11:38:15 +0100 Subject: [PATCH 180/721] Fix usage of $service_name --- data/helpers.d/backend | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 66f10baba..95ef95820 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -158,8 +158,8 @@ ynh_remove_systemd_config () { local finalsystemdconf="/etc/systemd/system/$service.service" if [ -e "$finalsystemdconf" ]; then - ynh_systemd_action --service_name=$service_name --action=stop - systemctl disable $service_name + ynh_systemd_action --service_name=$service --action=stop + systemctl disable $service ynh_secure_remove --file="$finalsystemdconf" systemctl daemon-reload fi From e0aaf6f8a160f7b2028635e9e1b7235eca6bc4c7 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Sat, 9 Feb 2019 11:56:33 +0100 Subject: [PATCH 181/721] add trace for process check --- data/hooks/conf_regen/15-nginx | 1 + 1 file changed, 1 insertion(+) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 97543dcfa..f0ca11797 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -49,6 +49,7 @@ do_pre_regen() { mkdir -p "$mail_autoconfig_dir" # NGINX server configuration + echo "RDS Using security_ciphers_compatibility=$security_ciphers_compatibility" export security_ciphers_compatibility export domain export domain_cert_ca=$(yunohost domain cert-status $domain --json \ From a742ca8939d1d136d1b998c96d7e4fb4046dcc3c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 Feb 2019 15:40:29 +0100 Subject: [PATCH 182/721] Attempt to get rid of annoying 'unable to initialize frontend' messages --- data/helpers.d/package | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/package b/data/helpers.d/package index c0617abb2..1b7c38c5c 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -150,7 +150,7 @@ ynh_package_install_from_equivs () { cp "$controlfile" "${TMPDIR}/control" (cd "$TMPDIR" equivs-build ./control 1> /dev/null - dpkg --force-depends -i "./${pkgname}_${pkgversion}_all.deb" 2>&1) + DEBIAN_FRONTEND=noninteractive dpkg --force-depends -i "./${pkgname}_${pkgversion}_all.deb" 2>&1) ynh_package_install -f || ynh_die --message="Unable to install dependencies" [[ -n "$TMPDIR" ]] && rm -rf $TMPDIR # Remove the temp dir. From fc0e3d4830ddbcf1565380833e30292dffa1f322 Mon Sep 17 00:00:00 2001 From: Taekiro Date: Sat, 9 Feb 2019 16:37:23 +0100 Subject: [PATCH 183/721] Allow query to local IPv6 --- data/templates/dnsmasq/plain/dnsmasq.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/data/templates/dnsmasq/plain/dnsmasq.conf b/data/templates/dnsmasq/plain/dnsmasq.conf index 12a14048a..2dc7775e1 100644 --- a/data/templates/dnsmasq/plain/dnsmasq.conf +++ b/data/templates/dnsmasq/plain/dnsmasq.conf @@ -2,5 +2,6 @@ domain-needed expand-hosts listen-address=127.0.0.1 +listen-address=::1 resolv-file=/etc/resolv.dnsmasq.conf cache-size=256 From d0fbcb43454397dc98d0aef64327e5bd2d60276d Mon Sep 17 00:00:00 2001 From: Taekiro Date: Sat, 9 Feb 2019 16:42:59 +0100 Subject: [PATCH 184/721] Update dnsmasq.conf --- data/templates/dnsmasq/plain/dnsmasq.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/data/templates/dnsmasq/plain/dnsmasq.conf b/data/templates/dnsmasq/plain/dnsmasq.conf index 2dc7775e1..12a14048a 100644 --- a/data/templates/dnsmasq/plain/dnsmasq.conf +++ b/data/templates/dnsmasq/plain/dnsmasq.conf @@ -2,6 +2,5 @@ domain-needed expand-hosts listen-address=127.0.0.1 -listen-address=::1 resolv-file=/etc/resolv.dnsmasq.conf cache-size=256 From 6ba12bb9ae77c60653969b6bc999d6be739f7685 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sat, 9 Feb 2019 23:04:21 +0100 Subject: [PATCH 185/721] Fix ynh_local_curl --- data/helpers.d/utils | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 360174847..94ba8bd97 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -257,7 +257,15 @@ ynh_setup_source () { # | arg: ... - (Optionnal) More POST keys and values ynh_local_curl () { # Define url of page to curl - local full_page_url=https://localhost$path_url$1 + path_url=$(ynh_normalize_url_path $path_url) + local_page=$(ynh_normalize_url_path $1) + full_path=$path_url$local_page + + if [ "${path_url}" == "/" ]; then + full_path=$local_page + fi + + local full_page_url=https://localhost$full_path # Concatenate all other arguments with '&' to prepare POST data local POST_data="" From a0dfae269f2d1c6f570d55cdeef2d8a495a73a59 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sat, 9 Feb 2019 23:13:48 +0100 Subject: [PATCH 186/721] Update utils --- data/helpers.d/utils | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 94ba8bd97..40bef7b88 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -258,8 +258,8 @@ ynh_setup_source () { ynh_local_curl () { # Define url of page to curl path_url=$(ynh_normalize_url_path $path_url) - local_page=$(ynh_normalize_url_path $1) - full_path=$path_url$local_page + local local_page=$(ynh_normalize_url_path $1) + local full_path=$path_url$local_page if [ "${path_url}" == "/" ]; then full_path=$local_page From f084de5a69e7edd353ab90b7bf999775bbd234e0 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Sat, 9 Feb 2019 23:30:40 +0100 Subject: [PATCH 187/721] declare setting security.ciphers.compatibility --- locales/en.json | 1 + locales/fr.json | 1 + src/yunohost/settings.py | 1 + 3 files changed, 3 insertions(+) diff --git a/locales/en.json b/locales/en.json index 8528c2576..72a34fb57 100644 --- a/locales/en.json +++ b/locales/en.json @@ -202,6 +202,7 @@ "global_settings_setting_example_enum": "Example enum option", "global_settings_setting_example_int": "Example int option", "global_settings_setting_example_string": "Example string option", + "global_settings_setting_security_ciphers_compatibility": "Admin ciphers compatibility strategy", "global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_security_password_user_strength": "User password strength", "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json", diff --git a/locales/fr.json b/locales/fr.json index 7119039db..0c73cebcd 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -308,6 +308,7 @@ "global_settings_setting_example_int": "Exemple d’option de type entier", "global_settings_setting_example_string": "Exemple d’option de type chaîne", "global_settings_setting_example_enum": "Exemple d’option de type énumération", + "global_settings_setting_security_ciphers_compatibility": "Stratégie de compatibilité des ciphers", "global_settings_unknown_type": "Situation inattendue, la configuration {setting:s} semble avoir le type {unknown_type:s} mais ce n’est pas un type pris en charge par le système.", "global_settings_unknown_setting_from_settings_file": "Clef inconnue dans les configurations : {setting_key:s}, rejet de cette clef et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index bbfb3ca56..7826b620f 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -39,6 +39,7 @@ DEFAULTS = OrderedDict([ ("security.password.admin.strength", {"type": "int", "default": 1}), ("security.password.user.strength", {"type": "int", "default": 1}), ("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", "default": False}), + ("security.ciphers.compatibility", {"type": "string", "default": "intermediate"}), ]) From 76e1c2e3955f599d51174a8bf8a68d64a301f8bf Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 10 Feb 2019 20:51:40 +0100 Subject: [PATCH 188/721] Fix template variable in ynh_add_systemd_config Merged as a micro decision, as it's only a small mistake. --- data/helpers.d/backend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 5e6d564cf..c96315238 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -121,7 +121,7 @@ ynh_add_systemd_config () { # Manage arguments with getopts ynh_handle_getopts_args "$@" local service="${service:-$app}" - local template="${nonappend:-systemd.service}" + local template="${template:-systemd.service}" finalsystemdconf="/etc/systemd/system/$service.service" ynh_backup_if_checksum_is_different --file="$finalsystemdconf" From 573fab426834c575596e93c0f4ea7403fd47a4f4 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 10 Feb 2019 21:23:56 +0100 Subject: [PATCH 189/721] [fix] Escape double quote before eval in getopts Work in progress... Need to be tested. --- data/helpers.d/getopts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index efaa8d065..7055325f1 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -150,6 +150,9 @@ ynh_handle_getopts_args () { # If there's already another value for this option, add a ; before adding the new value eval ${option_var}+="\;" fi + # Escape double quote to prevent any interpretation during the eval + all_args[$i]="${all_args[$i]//\"/\\\"}" + eval ${option_var}+=\"${all_args[$i]}\" shift_value=$(( shift_value + 1 )) fi @@ -188,6 +191,9 @@ ynh_handle_getopts_args () { # The variable name will be stored in 'option_var' local option_var="${args_array[$option_flag]%=}" + # Escape double quote to prevent any interpretation during the eval + arguments[$i]="${arguments[$i]//\"/\\\"}" + # Store each value given as argument in the corresponding variable # The values will be stored in the same order than $args_array eval ${option_var}+=\"${arguments[$i]}\" From 3dbe9af7ddbaa082b22f385de13529384a6e0c19 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 10 Feb 2019 23:43:27 +0100 Subject: [PATCH 190/721] Create debug --- data/helpers.d/debug | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 data/helpers.d/debug diff --git a/data/helpers.d/debug b/data/helpers.d/debug new file mode 100644 index 000000000..a8b7c8d69 --- /dev/null +++ b/data/helpers.d/debug @@ -0,0 +1,59 @@ +#!/bin/bash + +# Debugger for app packagers +# +# usage: ynh_debug [--message=message] [--trace=1/0] +# | arg: -m, --message= - The text to print +# | arg: -t, --trace= - Turn on or off the trace of the script. Usefull to trace nonly a small part of a script. +ynh_debug () { + # Disable set xtrace for the helper itself, to not pollute the debug log + set +x + # Declare an array to define the options of this helper. + local legacy_args=mt + declare -Ar args_array=( [m]=message= [t]=trace= ) + local message + local trace + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + # Redisable xtrace, ynh_handle_getopts_args set it back + set +x + message=${message:-} + trace=${trace:-} + + if [ -n "$message" ] + then + ynh_print_log "\e[34m\e[1m[DEBUG]\e[0m ${message}" >&2 + fi + + if [ "$trace" == "1" ] + then + ynh_debug --message="Enable debugging" + set +x + # Get the current file descriptor of xtrace + old_bash_xtracefd=$BASH_XTRACEFD + # Add the current file name and the line number of any command currently running while tracing. + PS4='$(basename ${BASH_SOURCE[0]})-L${LINENO}: ' + # Force xtrace to stderr + BASH_XTRACEFD=2 + fi + if [ "$trace" == "0" ] + then + ynh_debug --message="Disable debugging" + set +x + # Put xtrace back to its original fild descriptor + BASH_XTRACEFD=$old_bash_xtracefd + fi + # Renable set xtrace + set -x +} + +# Execute a command and print the result as debug +# +# usage: ynh_debug_exec command to execute +# usage: ynh_debug_exec "command to execute | following command" +# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. +# +# | arg: command - command to execute +ynh_debug_exec () { + ynh_debug --message="$(eval $@)" +} From b6c5b1fce933847a3a29e7e26c747792271b13b6 Mon Sep 17 00:00:00 2001 From: Kayou Date: Mon, 11 Feb 2019 16:45:40 +0100 Subject: [PATCH 191/721] Don't normilize path_url --- data/helpers.d/utils | 1 - 1 file changed, 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 40bef7b88..5ba2946a2 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -257,7 +257,6 @@ ynh_setup_source () { # | arg: ... - (Optionnal) More POST keys and values ynh_local_curl () { # Define url of page to curl - path_url=$(ynh_normalize_url_path $path_url) local local_page=$(ynh_normalize_url_path $1) local full_path=$path_url$local_page From 6ab5d716037b76a913c30a43b6ad1e2d57870428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 11 Feb 2019 20:05:21 +0100 Subject: [PATCH 192/721] Add the possibility to have multiple path per hook name --- src/yunohost/hook.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 02e3cb2dd..d9cad9c7a 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -283,14 +283,16 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None, no_trace=no_trace, raise_on_error=True)[1] except YunohostError as e: state = 'failed' + hook_return = {} logger.error(e.strerror, exc_info=1) post_callback(name=name, priority=priority, path=path, succeed=False) else: post_callback(name=name, priority=priority, path=path, succeed=True) - - result[name] = {'path' : path, 'state' : state, 'stdreturn' : hook_return } + if not name in result: + result[name] = {} + result[name][path] = {'state' : state, 'stdreturn' : hook_return } return result @@ -389,7 +391,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, raise YunohostError('hook_exec_not_terminated', path=path) else: logger.error(m18n.n('hook_exec_not_terminated', path=path)) - return 1, '' + return 1, {} elif raise_on_error and returncode != 0: raise YunohostError('hook_exec_failed', path=path) From 2065b8914262d09bcaee92fb1c9fba7b4ab87ffa Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 12 Feb 2019 00:28:39 +0100 Subject: [PATCH 193/721] Use jq instead of python3 --- data/helpers.d/system | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index a93b3ea6f..39af0092f 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -67,7 +67,7 @@ ynh_read_manifest () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - python3 -c "import sys, json;print(json.load(open('$manifest', encoding='utf-8'))['$manifest_key'])" + jq ".$manifest_key" "$manifest" --raw-output } # Read the upstream version from the manifest From d15092a7401001ffbe8fcceec168bab0657ea439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 11 Feb 2019 20:22:42 +0100 Subject: [PATCH 194/721] Fix result from hook_callback --- src/yunohost/domain.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 088f7724f..66f17c491 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -206,16 +206,16 @@ def domain_dns_conf(domain, ttl=None): result += "; Mail" for record in dns_conf["mail"]: result += "\n{name} {ttl} IN {type} {value}".format(**record) - result += "\n\n + result += "\n\n" result += "; Extra" for record in dns_conf["extra"]: result += "\n{name} {ttl} IN {type} {value}".format(**record) for name, record_list in dns_conf.items(): - if name not in ("basic", "xmpp", "mail"): + if name not in ("basic", "xmpp", "mail", "extra") and record_list: result += "\n\n" - result += "; " + name + "\n" + result += "; " + name for record in record_list: result += "\n{name} {ttl} IN {type} {value}".format(**record) @@ -418,8 +418,10 @@ def _build_dns_conf(domain, ttl=3600): # Custom record hookres = hook_callback('custom_dns_rules', args=[domain]) - for n,v in hookres.items() : - res[n] = v['stdreturn'] + for n, val in hookres.items() : + res[n] = [] + for v in [v['stdreturn'] for p, v in val.items() if v and v['stdreturn']]: + res[n].extend(v) return res From 37a2cc2e1c155f420f283d05bb0eecdf7ad6d1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 11 Feb 2019 20:05:50 +0100 Subject: [PATCH 195/721] Adapt the service and backup to support new result of hook_callback result --- src/yunohost/backup.py | 30 +++++++++++++++++++----------- src/yunohost/service.py | 5 +++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 3874a4461..d33f75e3a 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -593,8 +593,11 @@ class BackupManager(): env=env_dict, chdir=self.work_dir) - if ret["succeed"] != []: - self.system_return = ret["succeed"] + ret_succeed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "succeed"] for n, v in ret.items()}.items() if val} + ret_failed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in ret.items()}.items() if val} + + if ret_succeed != []: + self.system_return = ret_succeed # Add files from targets (which they put in the CSV) to the list of # files to backup @@ -610,7 +613,7 @@ class BackupManager(): restore_hooks = hook_list("restore")["hooks"] - for part in ret['succeed'].keys(): + for part in ret_succeed.keys(): if part in restore_hooks: part_restore_hooks = hook_info("restore", part)["hooks"] for hook in part_restore_hooks: @@ -620,7 +623,7 @@ class BackupManager(): logger.warning(m18n.n('restore_hook_unavailable', hook=part)) self.targets.set_result("system", part, "Warning") - for part in ret['failed'].keys(): + for part in ret_failed.keys(): logger.error(m18n.n('backup_system_part_failed', part=part)) self.targets.set_result("system", part, "Error") @@ -1177,16 +1180,19 @@ class RestoreManager(): env=env_dict, chdir=self.work_dir) - for part in ret['succeed'].keys(): + ret_succeed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "succeed"] for n, v in ret.items()}.items() if val} + ret_failed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in ret.items()}.items() if val} + + for part in ret_succeed.keys(): self.targets.set_result("system", part, "Success") error_part = [] - for part in ret['failed'].keys(): + for part in ret_failed.keys(): logger.error(m18n.n('restore_system_part_failed', part=part)) self.targets.set_result("system", part, "Error") error_part.append(part) - if ret['failed']: + if ret_failed: operation_logger.error(m18n.n('restore_system_part_failed', part=', '.join(error_part))) else: operation_logger.success() @@ -1929,8 +1935,8 @@ class CustomBackupMethod(BackupMethod): ret = hook_callback('backup_method', [self.method], args=self._get_args('need_mount')) - - self._need_mount = True if ret['succeed'] else False + ret_succeed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "succeed"] for n, v in ret.items()}.items() if val} + self._need_mount = True if ret_succeed else False return self._need_mount def backup(self): @@ -1943,7 +1949,8 @@ class CustomBackupMethod(BackupMethod): ret = hook_callback('backup_method', [self.method], args=self._get_args('backup')) - if ret['failed']: + ret_failed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in ret.items()}.items() if val} + if ret_failed: raise YunohostError('backup_custom_backup_error') def mount(self, restore_manager): @@ -1956,7 +1963,8 @@ class CustomBackupMethod(BackupMethod): super(CustomBackupMethod, self).mount(restore_manager) ret = hook_callback('backup_method', [self.method], args=self._get_args('mount')) - if ret['failed']: + ret_failed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in ret.items()}.items() if val} + if ret_failed: raise YunohostError('backup_custom_mount_error') def _get_args(self, action): diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 60729053b..151a877b9 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -494,11 +494,12 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False, pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) # Update the services name - names = pre_result['succeed'].keys() + names = {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in pre_result.items()}.keys() if not names: + ret_failed = {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in pre_result.items()} raise YunohostError('service_regenconf_failed', - services=', '.join(pre_result['failed'])) + services=', '.join(ret_failed)) # Set the processing method _regen = _process_regen_conf if not dry_run else lambda *a, **k: True From 2f1a517a428849c0a252735bee1d5f59c8b13814 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 12 Feb 2019 20:17:15 +0100 Subject: [PATCH 196/721] Print a diff of the files when backup by ynh_backup_if_checksum_is_different --- data/helpers.d/filesystem | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 88d51750b..903f4b6a0 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -352,7 +352,8 @@ ynh_backup_if_checksum_is_different () { local backup_file="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" sudo mkdir -p "$(dirname "$backup_file")" sudo cp -a "$file" "$backup_file" # Backup the current file - echo "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file" >&2 + ynh_print_info "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file" + ynh_print_info "$(git diff --no-index --patch-with-stat $backup_file $file)" echo "$backup_file" # Return the name of the backup file fi fi From 73986ab10f2f44ceaff3c8f33460368461017066 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 12 Feb 2019 21:47:40 +0100 Subject: [PATCH 197/721] Be clever... diff the file after modification... --- data/helpers.d/filesystem | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 903f4b6a0..3f2b69ed2 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -323,6 +323,12 @@ ynh_store_file_checksum () { local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(sudo md5sum "$file" | cut -d' ' -f1) + + if [ -n "${backup_file_checksum-}" ] + then + ynh_print_info "$(diff --report-identical-files --unified --color=always $backup_file_checksum $file)" + fi + unset backup_file_checksum } # Verify the checksum and backup the file if it's different @@ -345,16 +351,16 @@ ynh_backup_if_checksum_is_different () { local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name) + backup_file_checksum="" if [ -n "$checksum_value" ] then # Proceed only if a value was stored into the app settings if ! echo "$checksum_value $file" | sudo md5sum -c --status then # If the checksum is now different - local backup_file="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" - sudo mkdir -p "$(dirname "$backup_file")" - sudo cp -a "$file" "$backup_file" # Backup the current file - ynh_print_info "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file" - ynh_print_info "$(git diff --no-index --patch-with-stat $backup_file $file)" - echo "$backup_file" # Return the name of the backup file + backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" + sudo mkdir -p "$(dirname "$backup_file_checksum")" + sudo cp -a "$file" "$backup_file_checksum" # Backup the current file + ynh_print_info "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum" + echo "$backup_file_checksum" # Return the name of the backup file fi fi } From 6c925c04c334b3b17ebde3e5e461648e4e20d5a5 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 12 Feb 2019 22:08:37 +0100 Subject: [PATCH 198/721] Fail with ynh_print_info --- data/helpers.d/filesystem | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 3f2b69ed2..713c3890b 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -324,10 +324,14 @@ ynh_store_file_checksum () { local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(sudo md5sum "$file" | cut -d' ' -f1) + # If backup_file_checksum isn't empty, ynh_backup_if_checksum_is_different has made a backup if [ -n "${backup_file_checksum-}" ] then - ynh_print_info "$(diff --report-identical-files --unified --color=always $backup_file_checksum $file)" + # Print the diff between the previous file and the new one. + # diff return 1 if the files are different, so the || true + diff --report-identical-files --unified --color=always $backup_file_checksum $file >&2 || true fi + # Unset the variable, so it wouldn't trig a ynh_store_file_checksum without a ynh_backup_if_checksum_is_different before it. unset backup_file_checksum } @@ -351,6 +355,7 @@ ynh_backup_if_checksum_is_different () { local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name) + # backup_file_checksum isn't declare as local, so it can be reuse by ynh_store_file_checksum backup_file_checksum="" if [ -n "$checksum_value" ] then # Proceed only if a value was stored into the app settings From 140ae8e51a74e9db693b954ea22e08038ff1f5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 12 Feb 2019 21:13:49 +0100 Subject: [PATCH 199/721] Change the way to manage the jail and filter file and improve documentations Using a template file make more easy to use a custom failregex. It also give the possiblitity to use custom settings in the fail2ban config --- data/helpers.d/backend | 116 ++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 47 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 6b5ca1d37..ac4d4ed94 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -186,59 +186,85 @@ ynh_remove_fpm_config () { # Create a dedicated fail2ban config (jail and filter conf files) # -# usage: ynh_add_fail2ban_config log_file filter [max_retry [ports]] -# | arg: -l, --logpath= - Log file to be checked by fail2ban -# | arg: -r, --failregex= - Failregex to be looked for by fail2ban -# | arg: -m, --max_retry= - Maximum number of retries allowed before banning IP address - default: 3 -# | arg: -p, --ports= - Ports blocked for a banned IP address - default: http,https +# usage: ynh_add_fail2ban_config "list of others variables to replace" +# +# | arg: list of others variables to replace separeted by a space +# | for example : 'var_1 var_2 ...' +# +# This will use a template in ../conf/f2b_jail.conf and ../conf/f2b_filter.conf +# __APP__ by $app +# +# You can dynamically replace others variables by example : +# __VAR_1__ by $var_1 +# __VAR_2__ by $var_2 +# +# Note about the "failregex" option: +# regex to match the password failure messages in the logfile. The +# host must be matched by a group named "host". The tag "" can +# be used for standard IP/hostname matching and is only an alias for +# (?:::f{4,6}:)?(?P[\w\-.^_]+) +# +# You can find some more explainations about how to make a regex here : +# https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Filters +# +# Note that the logfile need to exist before to call this helper !! +# +# Generally your template will look like that by example (for synapse): +# +# f2b_jail.conf: +# [__APP__] +# enabled = true +# port = http,https +# filter = __APP__ +# logpath = /var/log/__APP__/logfile.log +# maxretry = 3 +# +# f2b_filter.conf: +# [INCLUDES] +# before = common.conf +# [Definition] +# +# # Part of regex definition (just used to make more easy to make the global regex) +# __synapse_start_line = .? \- synapse\..+ \- +# +# # Regex definition. +# failregex = ^%(__synapse_start_line)s INFO \- POST\-(\d+)\- \- \d+ \- Received request\: POST /_matrix/client/r0/login\??%(__synapse_start_line)s INFO \- POST\-\1\- Got login request with identifier: \{u'type': u'm.id.user', u'user'\: u'(.+?)'\}, medium\: None, address: None, user\: u'\5'%(__synapse_start_line)s WARNING \- \- (Attempted to login as @\5\:.+ but they do not exist|Failed password login for user @\5\:.+)$ +# +# ignoreregex = +# +# To validate your regex you can test with this command: +# fail2ban-regex /var/log/YOUR_LOG_FILE_PATH /etc/fail2ban/filter.d/YOUR_APP.conf ynh_add_fail2ban_config () { - # Declare an array to define the options of this helper. - declare -Ar args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= ) - local logpath - local failregex - local max_retry - local ports - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - max_retry=${max_retry:-3} - ports=${ports:-http,https} - - test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." - test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." + local others_var=${1:-} finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" - ynh_backup_if_checksum_is_different "$finalfail2banjailconf" 1 - ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" 1 + ynh_backup_if_checksum_is_different "$finalfail2banjailconf" + ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" - tee $finalfail2banjailconf <&2 echo "WARNING${fail2ban_error#*WARNING}" >&2 fi @@ -250,9 +276,5 @@ EOF ynh_remove_fail2ban_config () { ynh_secure_remove "/etc/fail2ban/jail.d/$app.conf" ynh_secure_remove "/etc/fail2ban/filter.d/$app.conf" - if [ "$(lsb_release --codename --short)" != "jessie" ]; then - systemctl reload fail2ban - else - systemctl restart fail2ban - fi + systemctl try-reload-or-restart fail2ban } From e1ccab212a7241d1e76b955b6699c20c10bfc7c0 Mon Sep 17 00:00:00 2001 From: Josue-T Date: Wed, 13 Feb 2019 22:17:32 +0100 Subject: [PATCH 200/721] Reload fail2ban instead of restart --- src/yunohost/firewall.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/firewall.py b/src/yunohost/firewall.py index 1c44efe99..9d209dbb8 100644 --- a/src/yunohost/firewall.py +++ b/src/yunohost/firewall.py @@ -195,6 +195,7 @@ def firewall_reload(skip_upnp=False): """ from yunohost.hook import hook_callback + from yunohost.service import _run_service_command reloaded = False errors = False @@ -276,8 +277,7 @@ def firewall_reload(skip_upnp=False): # Refresh port forwarding with UPnP firewall_upnp(no_refresh=False) - # TODO: Use service_restart - os.system("service fail2ban restart") + _run_service_command("reload", "fail2ban") if errors: logger.warning(m18n.n('firewall_rules_cmd_failed')) From e776c777e602f76f614659353d9e0f8d84acb387 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 21:17:13 +0100 Subject: [PATCH 201/721] remove strip whitespace syntax --- data/templates/nginx/server.tpl.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 50ee1b9b8..2d161d79c 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -29,7 +29,7 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; - {%- if security_ciphers_compatibility == "modern" -%} + {% if security_ciphers_compatibility == "modern" %} # Ciphers with modern compatibility # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) @@ -37,7 +37,7 @@ server { ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; ssl_prefer_server_ciphers on; - {%- else -%} + {% else %} # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 ssl_ecdh_curve secp521r1:secp384r1:prime256v1; ssl_prefer_server_ciphers on; @@ -50,7 +50,7 @@ server { # Uncomment the following directive after DH generation # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; - {%- endif -%} + {% endif %} # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners # https://wiki.mozilla.org/Security/Guidelines/Web_Security From 3251189ab8529e34c455f38b2b88d60fe47b8208 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 21:26:25 +0100 Subject: [PATCH 202/721] what a cumbersome whitespace control --- data/templates/nginx/server.tpl.conf | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 2d161d79c..5f22c8df5 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -29,14 +29,13 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; - {% if security_ciphers_compatibility == "modern" %} + {% if security_ciphers_compatibility == "modern" -%} # Ciphers with modern compatibility # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) ssl_protocols TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; ssl_prefer_server_ciphers on; - {% else %} # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 ssl_ecdh_curve secp521r1:secp384r1:prime256v1; @@ -64,7 +63,7 @@ server { more_set_headers "X-Permitted-Cross-Domain-Policies : none"; more_set_headers "X-Frame-Options : SAMEORIGIN"; - {% if domain_cert_ca == "Let's Encrypt" %} + {% if domain_cert_ca == "Let's Encrypt" -%} # OCSP settings ssl_stapling on; ssl_stapling_verify on; From a267e1bc74167f07bb521162f4e92b225336e89d Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 21:28:56 +0100 Subject: [PATCH 203/721] what a cumbersome whitespace control --- data/templates/nginx/server.tpl.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 5f22c8df5..9d662b904 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -70,7 +70,7 @@ server { ssl_trusted_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; resolver 127.0.0.1 127.0.1.1 valid=300s; resolver_timeout 5s; - {% endif %} + {%- endif %} access_by_lua_file /usr/share/ssowat/access.lua; From 7b01ccfefbc534c34b31dcb3367ebf629b01353c Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 21:33:36 +0100 Subject: [PATCH 204/721] remove whitespace control attempt --- data/templates/nginx/server.tpl.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 9d662b904..e19bab970 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -29,7 +29,7 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; - {% if security_ciphers_compatibility == "modern" -%} + {% if security_ciphers_compatibility == "modern" %} # Ciphers with modern compatibility # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) @@ -63,14 +63,14 @@ server { more_set_headers "X-Permitted-Cross-Domain-Policies : none"; more_set_headers "X-Frame-Options : SAMEORIGIN"; - {% if domain_cert_ca == "Let's Encrypt" -%} + {% if domain_cert_ca == "Let's Encrypt" %} # OCSP settings ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; resolver 127.0.0.1 127.0.1.1 valid=300s; resolver_timeout 5s; - {%- endif %} + {% endif %} access_by_lua_file /usr/share/ssowat/access.lua; From e9274ee44376cbdcb617f7aa9622d93f6672145a Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 22:27:49 +0100 Subject: [PATCH 205/721] Handle yunohost admin nginx config --- .../templates/nginx/plain/yunohost_admin.conf | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index b6fabf8e3..7992b2de9 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -20,6 +20,14 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; + {% if security_ciphers_compatibility == "modern" %} + # Ciphers with modern compatibility + # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern + # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) + ssl_protocols TLSv1.2; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_prefer_server_ciphers on; + {% else %} # As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519 ssl_ecdh_curve secp521r1:secp384r1:prime256v1; ssl_prefer_server_ciphers on; @@ -29,20 +37,15 @@ server { ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; - # Ciphers with modern compatibility - # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern - # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) - #ssl_protocols TLSv1.2; - #ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; - # Uncomment the following directive after DH generation # > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 #ssl_dhparam /etc/ssl/private/dh2048.pem; - + {% endif %} + # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners # https://wiki.mozilla.org/Security/Guidelines/Web_Security - # https://observatory.mozilla.org/ - more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; + # https://observatory.mozilla.org/ + more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; more_set_headers "Referrer-Policy : 'same-origin'"; more_set_headers "Content-Security-Policy : upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'"; more_set_headers "X-Content-Type-Options : nosniff"; From 12d0e0e1de00463fb13c468ef1403e6761a3cda7 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 22:34:35 +0100 Subject: [PATCH 206/721] add instruction to rebuild yunohost-admin conf --- data/hooks/conf_regen/15-nginx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index f0ca11797..4c5bac331 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -41,6 +41,8 @@ do_pre_regen() { security_ciphers_compatibility="$(yunohost settings get 'security.ciphers.compatibility')" fi + export security_ciphers_compatibility + # add domain conf files for domain in $domain_list; do domain_conf_dir="${nginx_conf_dir}/${domain}.d" @@ -49,8 +51,6 @@ do_pre_regen() { mkdir -p "$mail_autoconfig_dir" # NGINX server configuration - echo "RDS Using security_ciphers_compatibility=$security_ciphers_compatibility" - export security_ciphers_compatibility export domain export domain_cert_ca=$(yunohost domain cert-status $domain --json \ | jq ".certificates.\"$domain\".CA_type" \ @@ -64,6 +64,7 @@ do_pre_regen() { || cp yunohost_local.conf "${domain_conf_dir}/yunohost_local.conf" done + ynh_render_template "plain/yunohost_admin.conf" "${nginx_conf_dir}/yunohost-admin.conf" # remove old domain conf files conf_files=$(ls -1 /etc/nginx/conf.d \ From ec52ded7776cc481c7563edce790a1c20d5ee09e Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 22:38:28 +0100 Subject: [PATCH 207/721] don't conflict translation tool --- locales/fr.json | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 0c73cebcd..7119039db 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -308,7 +308,6 @@ "global_settings_setting_example_int": "Exemple d’option de type entier", "global_settings_setting_example_string": "Exemple d’option de type chaîne", "global_settings_setting_example_enum": "Exemple d’option de type énumération", - "global_settings_setting_security_ciphers_compatibility": "Stratégie de compatibilité des ciphers", "global_settings_unknown_type": "Situation inattendue, la configuration {setting:s} semble avoir le type {unknown_type:s} mais ce n’est pas un type pris en charge par le système.", "global_settings_unknown_setting_from_settings_file": "Clef inconnue dans les configurations : {setting_key:s}, rejet de cette clef et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", From e8eff8729792d9ef27cf8b829687206c941cd987 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 22:49:13 +0100 Subject: [PATCH 208/721] switch to enum type to store cipher policy setting --- src/yunohost/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 7826b620f..9b12647a6 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -39,7 +39,7 @@ DEFAULTS = OrderedDict([ ("security.password.admin.strength", {"type": "int", "default": 1}), ("security.password.user.strength", {"type": "int", "default": 1}), ("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", "default": False}), - ("security.ciphers.compatibility", {"type": "string", "default": "intermediate"}), + ("security.ciphers.compatibility", {"type": "enum", "choices": "intermediate", "modern"}), ]) From a899102efc8a13fcf4c16bf31bb7769f4d820b23 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 22:57:24 +0100 Subject: [PATCH 209/721] don't share setting for nginx and ssh --- data/hooks/conf_regen/03-ssh | 6 +++--- data/templates/ssh/sshd_config | 6 +++--- locales/en.json | 3 ++- src/yunohost/settings.py | 1 + 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index 330166f08..3a79de456 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -24,13 +24,13 @@ do_pre_regen() { fi # Support different strategy for security configurations - if [[ -n "$(yunohost settings get 'security.ciphers.compatibility')" ]]; then - security_ciphers_compatibility="$(yunohost settings get 'security.ciphers.compatibility')" + if [[ -n "$(yunohost settings get 'service.ssh.ciphers.compatibility')" ]]; then + ssh_ciphers_compatibility="$(yunohost settings get 'service.ssh.ciphers.compatibility')" fi export ssh_keys export ipv6_enabled - export security_ciphers_compatibility + export ssh_ciphers_compatibility ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config" } diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index 7194a309d..f27ca3ebe 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -15,17 +15,17 @@ HostKey {{ key }}{% endfor %} # https://infosec.mozilla.org/guidelines/openssh # ############################################## -{%- if security_ciphers_compatibility == "intermediate" -%} +{% if ssh_ciphers_compatibility == "intermediate" %} KexAlgorithms diffie-hellman-group-exchange-sha256 Ciphers aes256-ctr,aes192-ctr,aes128-ctr MACs hmac-sha2-512,hmac-sha2-256 -{%- else -%} +{% else %} # By default use "modern" Mozilla configuration # Keys, ciphers and MACS KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256 Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com -{%- endif -%} +{% endif %} # Use kernel sandbox mechanisms where possible in unprivileged processes UsePrivilegeSeparation sandbox diff --git a/locales/en.json b/locales/en.json index 72a34fb57..3f01cb08e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -202,11 +202,12 @@ "global_settings_setting_example_enum": "Example enum option", "global_settings_setting_example_int": "Example int option", "global_settings_setting_example_string": "Example string option", - "global_settings_setting_security_ciphers_compatibility": "Admin ciphers compatibility strategy", + "global_settings_setting_security_ciphers_compatibility": "Admin ciphers compatibility strategy for the web", "global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_security_password_user_strength": "User password strength", "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", + "global_settings_setting_service_ssh_ciphers_compatibility": "Admin ciphers compatibility strategy for SSH", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 9b12647a6..c3d4591b0 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -39,6 +39,7 @@ DEFAULTS = OrderedDict([ ("security.password.admin.strength", {"type": "int", "default": 1}), ("security.password.user.strength", {"type": "int", "default": 1}), ("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", "default": False}), + ("service.ssh.ciphers.compatibility", {"type": "enum", "choices": "intermediate", "modern"}), ("security.ciphers.compatibility", {"type": "enum", "choices": "intermediate", "modern"}), ]) From 375b7d53b1fca626abdba882799692aa247ab1e8 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 23:00:52 +0100 Subject: [PATCH 210/721] fix enum syntax --- src/yunohost/settings.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index c3d4591b0..1d60c3a6a 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -39,8 +39,10 @@ DEFAULTS = OrderedDict([ ("security.password.admin.strength", {"type": "int", "default": 1}), ("security.password.user.strength", {"type": "int", "default": 1}), ("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", "default": False}), - ("service.ssh.ciphers.compatibility", {"type": "enum", "choices": "intermediate", "modern"}), - ("security.ciphers.compatibility", {"type": "enum", "choices": "intermediate", "modern"}), + ("service.ssh.ciphers.compatibility", {"type": "enum", "default": "modern", + "choices": ["intermediate", "modern"]}), + ("security.ciphers.compatibility", {"type": "enum", "default": "intermediate", + "choices": ["intermediate", "modern"]}), ]) From aaf6dba8d7c28ca43dedc88b0eec907b22ed2629 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 23:03:27 +0100 Subject: [PATCH 211/721] fix yunhost admin config filename --- data/hooks/conf_regen/15-nginx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 4c5bac331..c33f16cba 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -64,7 +64,7 @@ do_pre_regen() { || cp yunohost_local.conf "${domain_conf_dir}/yunohost_local.conf" done - ynh_render_template "plain/yunohost_admin.conf" "${nginx_conf_dir}/yunohost-admin.conf" + ynh_render_template "plain/yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf" # remove old domain conf files conf_files=$(ls -1 /etc/nginx/conf.d \ From 5d48640f3c993d5a3d7c80a411531d4efc07892b Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 14 Feb 2019 23:12:59 +0100 Subject: [PATCH 212/721] Adapt comment to new context --- data/templates/nginx/server.tpl.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index e19bab970..84c884055 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -32,7 +32,7 @@ server { {% if security_ciphers_compatibility == "modern" %} # Ciphers with modern compatibility # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern - # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) + # The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) ssl_protocols TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; ssl_prefer_server_ciphers on; From bba393c45c5ee45a942f6c7be697e7a2415e14b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 15 Feb 2019 14:06:53 +0100 Subject: [PATCH 213/721] Add possibility to use template or predefined config --- data/helpers.d/backend | 70 +++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index ac4d4ed94..c16da8f95 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -185,11 +185,18 @@ ynh_remove_fpm_config () { } # Create a dedicated fail2ban config (jail and filter conf files) +# usage 1: ynh_add_fail2ban_config log_file filter [max_retry [ports]] +# | arg: -l, --logpath= - Log file to be checked by fail2ban +# | arg: -r, --failregex= - Failregex to be looked for by fail2ban +# | arg: -m, --max_retry= - Maximum number of retries allowed before banning IP address - default: 3 +# | arg: -p, --ports= - Ports blocked for a banned IP address - default: http,https # -# usage: ynh_add_fail2ban_config "list of others variables to replace" +# ----------------------------------------------------------------------------- # -# | arg: list of others variables to replace separeted by a space -# | for example : 'var_1 var_2 ...' +# usage 2: ynh_add_fail2ban_config -t [-v "list of others variables to replace"] +# | arg: -t, --use_template - Use this helper in template mode +# | arg: -v, --others_var= - List of others variables to replace separeted by a space +# | for example : 'var_1 var_2 ...' # # This will use a template in ../conf/f2b_jail.conf and ../conf/f2b_filter.conf # __APP__ by $app @@ -235,7 +242,16 @@ ynh_remove_fpm_config () { # To validate your regex you can test with this command: # fail2ban-regex /var/log/YOUR_LOG_FILE_PATH /etc/fail2ban/filter.d/YOUR_APP.conf ynh_add_fail2ban_config () { - local others_var=${1:-} + # Declare an array to define the options of this helper. + declare -Ar args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) + local logpath + local failregex + local max_retry + local ports + local others_var + local use_template + # Manage arguments with getopts + ynh_handle_getopts_args "$@" finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" @@ -245,18 +261,42 @@ ynh_add_fail2ban_config () { cp ../conf/f2b_jail.conf $finalfail2banjailconf cp ../conf/f2b_filter.conf $finalfail2banfilterconf - if test -n "${app:-}"; then - ynh_replace_string "__APP__" "$app" "$finalfail2banjailconf" - ynh_replace_string "__APP__" "$app" "$finalfail2banfilterconf" - fi + if [[ ${use_template:-0} == 1 ]]; then + if test -n "${app:-}"; then + ynh_replace_string "__APP__" "$app" "$finalfail2banjailconf" + ynh_replace_string "__APP__" "$app" "$finalfail2banfilterconf" + fi - # Replace all other variable given as arguments - for var_to_replace in $others_var; do - # ${var_to_replace^^} make the content of the variable on upper-cases - # ${!var_to_replace} get the content of the variable named $var_to_replace - ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banjailconf" - ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banfilterconf" - done + # Replace all other variable given as arguments + for var_to_replace in ${others_var:-}; do + # ${var_to_replace^^} make the content of the variable on upper-cases + # ${!var_to_replace} get the content of the variable named $var_to_replace + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banjailconf" + ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banfilterconf" + done + else + max_retry=${max_retry:-3} + ports=${ports:-http,https} + test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." + test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." + + tee $finalfail2banjailconf < Date: Fri, 15 Feb 2019 16:18:36 +0100 Subject: [PATCH 214/721] Update usage comment --- data/helpers.d/backend | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index c16da8f95..c0722a5ce 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -185,15 +185,15 @@ ynh_remove_fpm_config () { } # Create a dedicated fail2ban config (jail and filter conf files) -# usage 1: ynh_add_fail2ban_config log_file filter [max_retry [ports]] +# usage 1: ynh_add_fail2ban_config --logpath=log_file --failregex=filter [--max_retry=max_retry] [--ports=ports] # | arg: -l, --logpath= - Log file to be checked by fail2ban # | arg: -r, --failregex= - Failregex to be looked for by fail2ban # | arg: -m, --max_retry= - Maximum number of retries allowed before banning IP address - default: 3 -# | arg: -p, --ports= - Ports blocked for a banned IP address - default: http,https +# | arg: -p, --ports= - Ports blocked for a banned IP address - default: http,https # # ----------------------------------------------------------------------------- # -# usage 2: ynh_add_fail2ban_config -t [-v "list of others variables to replace"] +# usage 2: ynh_add_fail2ban_config --use_template [--others_var="list of others variables to replace"] # | arg: -t, --use_template - Use this helper in template mode # | arg: -v, --others_var= - List of others variables to replace separeted by a space # | for example : 'var_1 var_2 ...' From 54eb3efec0dce598cab7cc21446cda8485af25dc Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 15 Feb 2019 16:21:30 +0100 Subject: [PATCH 215/721] Reorganize comment header --- data/helpers.d/backend | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index c0722a5ce..a8da55ef7 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -185,6 +185,7 @@ ynh_remove_fpm_config () { } # Create a dedicated fail2ban config (jail and filter conf files) +# # usage 1: ynh_add_fail2ban_config --logpath=log_file --failregex=filter [--max_retry=max_retry] [--ports=ports] # | arg: -l, --logpath= - Log file to be checked by fail2ban # | arg: -r, --failregex= - Failregex to be looked for by fail2ban @@ -205,17 +206,6 @@ ynh_remove_fpm_config () { # __VAR_1__ by $var_1 # __VAR_2__ by $var_2 # -# Note about the "failregex" option: -# regex to match the password failure messages in the logfile. The -# host must be matched by a group named "host". The tag "" can -# be used for standard IP/hostname matching and is only an alias for -# (?:::f{4,6}:)?(?P[\w\-.^_]+) -# -# You can find some more explainations about how to make a regex here : -# https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Filters -# -# Note that the logfile need to exist before to call this helper !! -# # Generally your template will look like that by example (for synapse): # # f2b_jail.conf: @@ -239,8 +229,22 @@ ynh_remove_fpm_config () { # # ignoreregex = # +# ----------------------------------------------------------------------------- +# +# Note about the "failregex" option: +# regex to match the password failure messages in the logfile. The +# host must be matched by a group named "host". The tag "" can +# be used for standard IP/hostname matching and is only an alias for +# (?:::f{4,6}:)?(?P[\w\-.^_]+) +# +# You can find some more explainations about how to make a regex here : +# https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Filters +# +# Note that the logfile need to exist before to call this helper !! +# # To validate your regex you can test with this command: # fail2ban-regex /var/log/YOUR_LOG_FILE_PATH /etc/fail2ban/filter.d/YOUR_APP.conf +# ynh_add_fail2ban_config () { # Declare an array to define the options of this helper. declare -Ar args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) From ccb0cad296c335b63f9691e234c19ef2a2e11211 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 15 Feb 2019 16:32:13 +0100 Subject: [PATCH 216/721] Fix various issues --- data/helpers.d/backend | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index a8da55ef7..a7d3a09a1 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -256,17 +256,23 @@ ynh_add_fail2ban_config () { local use_template # Manage arguments with getopts ynh_handle_getopts_args "$@" + use_template="${use_template:-0}" + max_retry=${max_retry:-3} + ports=${ports:-http,https} finalfail2banjailconf="/etc/fail2ban/jail.d/$app.conf" finalfail2banfilterconf="/etc/fail2ban/filter.d/$app.conf" ynh_backup_if_checksum_is_different "$finalfail2banjailconf" ynh_backup_if_checksum_is_different "$finalfail2banfilterconf" - cp ../conf/f2b_jail.conf $finalfail2banjailconf - cp ../conf/f2b_filter.conf $finalfail2banfilterconf + if [ $use_template -eq 1 ] + then + # Usage 2, templates + cp ../conf/f2b_jail.conf $finalfail2banjailconf + cp ../conf/f2b_filter.conf $finalfail2banfilterconf - if [[ ${use_template:-0} == 1 ]]; then - if test -n "${app:-}"; then + if [ -n "${app:-}" ] + then ynh_replace_string "__APP__" "$app" "$finalfail2banjailconf" ynh_replace_string "__APP__" "$app" "$finalfail2banfilterconf" fi @@ -278,9 +284,9 @@ ynh_add_fail2ban_config () { ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banjailconf" ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalfail2banfilterconf" done + else - max_retry=${max_retry:-3} - ports=${ports:-http,https} + # Usage 1, no template. Build a config file from scratch. test -n "$logpath" || ynh_die "ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." test -n "$failregex" || ynh_die "ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." @@ -302,6 +308,7 @@ ignoreregex = EOF fi + # Common to usage 1 and 2. ynh_store_file_checksum "$finalfail2banjailconf" ynh_store_file_checksum "$finalfail2banfilterconf" @@ -309,8 +316,8 @@ EOF local fail2ban_error="$(journalctl -u fail2ban | tail -n50 | grep "WARNING.*$app.*")" if [[ -n "$fail2ban_error" ]]; then - echo "[ERR] Fail2ban failed to load the jail for $app" >&2 - echo "WARNING${fail2ban_error#*WARNING}" >&2 + ynh_print_err --message="Fail2ban failed to load the jail for $app" + ynh_print_warn --message="${fail2ban_error#*WARNING}" fi } From 0e3f3159cb23735b3971ad5d0d5f531de03c3bf3 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 16 Feb 2019 00:20:51 +0100 Subject: [PATCH 217/721] ynh_print_warn instead of info --- data/helpers.d/filesystem | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 713c3890b..10123dea4 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -364,7 +364,7 @@ ynh_backup_if_checksum_is_different () { backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')" sudo mkdir -p "$(dirname "$backup_file_checksum")" sudo cp -a "$file" "$backup_file_checksum" # Backup the current file - ynh_print_info "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum" + ynh_print_warn "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum" echo "$backup_file_checksum" # Return the name of the backup file fi fi From b62d72f77b31b9055e0a97aef68d3e2f19db30c9 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 16 Feb 2019 00:28:27 +0100 Subject: [PATCH 218/721] Factorize into ynh_read_manifest --- data/helpers.d/system | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index 39af0092f..052ea5cec 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -67,6 +67,11 @@ ynh_read_manifest () { # Manage arguments with getopts ynh_handle_getopts_args "$@" + if [ ! -e "$manifest" ]; then + # If the manifest isn't found, try the common place for backup and restore script. + manifest="../settings/manifest.json" + fi + jq ".$manifest_key" "$manifest" --raw-output } @@ -85,9 +90,6 @@ ynh_app_upstream_version () { ynh_handle_getopts_args "$@" manifest="${manifest:-../manifest.json}" - if [ ! -e "$manifest" ]; then - manifest="../settings/manifest.json" # Into the restore script, the manifest is not at the same place - fi version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") echo "${version_key/~ynh*/}" } @@ -107,9 +109,6 @@ ynh_app_package_version () { ynh_handle_getopts_args "$@" manifest="${manifest:-../manifest.json}" - if [ ! -e "$manifest" ]; then - manifest="../settings/manifest.json" # Into the restore script, the manifest is not at the same place - fi version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") echo "${version_key/*~ynh/}" } From 562b3b98f6817f6213dada93d24c2a7358d86b69 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Feb 2019 14:37:36 +0100 Subject: [PATCH 219/721] Improve messages when app upgrades start --- locales/en.json | 3 ++- src/yunohost/app.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 8528c2576..24841311a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -36,7 +36,8 @@ "app_sources_fetch_failed": "Unable to fetch sources files", "app_unknown": "Unknown app", "app_unsupported_remote_type": "Unsupported remote type used for the app", - "app_upgrade_app_name": "Upgrading app {app}…", + "app_upgrade_several_apps": "The following apps will be upgraded : {apps}", + "app_upgrade_app_name": "Now upgrading app {app}…", "app_upgrade_failed": "Unable to upgrade {app:s}", "app_upgrade_some_app_failed": "Unable to upgrade some applications", "app_upgraded": "{app:s} has been upgraded", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0bca68787..23b4b49b7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -586,7 +586,10 @@ def app_upgrade(auth, app=[], url=None, file=None): elif not isinstance(app, list): apps = [app] - logger.info("Upgrading apps %s", ", ".join(app)) + if len(apps) == 0: + raise YunohostError('app_no_upgrade') + if len(apps) > 1: + logger.info(m18n.n("app_upgrade_several_apps", apps=", ".join(app))) for app_instance_name in apps: logger.info(m18n.n('app_upgrade_app_name', app=app_instance_name)) From 7d3319332dbfd3e3ac1527b82fbb763261c2554b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Feb 2019 14:38:39 +0100 Subject: [PATCH 220/721] Improve message when some upgrades fail --- locales/en.json | 3 ++- src/yunohost/app.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 24841311a..3681fd847 100644 --- a/locales/en.json +++ b/locales/en.json @@ -24,7 +24,8 @@ "app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'", "app_location_unavailable": "This url is not available or conflicts with the already installed app(s):\n{apps:s}", "app_manifest_invalid": "Invalid app manifest: {error}", - "app_no_upgrade": "No app to upgrade", + "app_no_upgrade": "No apps to upgrade", + "app_not_upgraded": "The following apps were not upgraded: {apps}", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_installed": "{app:s} is not installed", "app_not_properly_removed": "{app:s} has not been properly removed", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 23b4b49b7..609bece5e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -575,6 +575,7 @@ def app_upgrade(auth, app=[], url=None, file=None): raise YunohostError('app_no_upgrade') upgraded_apps = [] + not_upgraded_apps = [] apps = app user_specified_list = True @@ -651,6 +652,7 @@ def app_upgrade(auth, app=[], url=None, file=None): if hook_exec(extracted_app_folder + '/scripts/upgrade', args=args_list, env=env_dict) != 0: msg = m18n.n('app_upgrade_failed', app=app_instance_name) + not_upgraded_apps.append(app_instance_name) logger.error(msg) operation_logger.error(msg) else: @@ -684,8 +686,8 @@ def app_upgrade(auth, app=[], url=None, file=None): hook_callback('post_app_upgrade', args=args_list, env=env_dict) operation_logger.success() - if not upgraded_apps: - raise YunohostError('app_no_upgrade') + if not_upgraded_apps: + raise YunohostError('app_not_upgraded', apps=', '.join(not_upgraded_apps)) app_ssowatconf(auth) From fe37acd315749be005c95f7f0a107b9d8a2afdd2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Feb 2019 14:45:05 +0100 Subject: [PATCH 221/721] Avoid checking for duplicates --- src/yunohost/app.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 609bece5e..5e7f17b6e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -574,7 +574,6 @@ def app_upgrade(auth, app=[], url=None, file=None): except YunohostError: raise YunohostError('app_no_upgrade') - upgraded_apps = [] not_upgraded_apps = [] apps = app @@ -587,6 +586,9 @@ def app_upgrade(auth, app=[], url=None, file=None): elif not isinstance(app, list): apps = [app] + # Remove possible duplicates + apps = [app for i,app in enumerate(apps) if apps not in L[:i]] + if len(apps) == 0: raise YunohostError('app_no_upgrade') if len(apps) > 1: @@ -598,9 +600,6 @@ def app_upgrade(auth, app=[], url=None, file=None): if not installed: raise YunohostError('app_not_installed', app=app_instance_name) - if app_instance_name in upgraded_apps: - continue - app_dict = app_info(app_instance_name, raw=True) if file: @@ -680,7 +679,6 @@ def app_upgrade(auth, app=[], url=None, file=None): os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path)) # So much win - upgraded_apps.append(app_instance_name) logger.success(m18n.n('app_upgraded', app=app_instance_name)) hook_callback('post_app_upgrade', args=args_list, env=env_dict) From af82be4ffdc68e5514fda773a750566f3972558f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 16 Feb 2019 15:43:09 +0100 Subject: [PATCH 222/721] Check if file exist --- data/helpers.d/system | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index 24e89dccb..71db85390 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -89,14 +89,14 @@ ynh_systemd_action() { if [[ -n "${line_match:-}" ]] then local templog="$(mktemp)" - # Following the starting of the app in its log - if [ "$log_path" == "systemd" ] ; then + # Following the starting of the app in its log + if [ "$log_path" == "systemd" ] ; then # Read the systemd journal journalctl --unit=$service_name --follow --since=-0 --quiet > "$templog" & - else + else # Read the specified log file tail -F -n0 "$log_path" > "$templog" & - fi + fi # Get the PID of the tail command local pid_tail=$! fi @@ -104,7 +104,7 @@ ynh_systemd_action() { echo "${action^} the service $service_name" >&2 systemctl $action $service_name \ || ( journalctl --lines=$length -u $service_name >&2 \ - ; test -n "$log_path" && echo "--" && tail --lines=$length "$log_path" >&2 \ + ; test -e "$log_path" && echo "--" && tail --lines=$length "$log_path" >&2 \ ; false ) # Start the timeout and try to find line_match @@ -127,7 +127,7 @@ ynh_systemd_action() { echo "The service $service_name didn't fully started before the timeout." >&2 echo "Please find here an extract of the end of the log of the service $service_name:" journalctl --lines=$length -u $service_name >&2 - test -n "$log_path" && echo "--" && tail --lines=$length "$log_path" >&2 + test -e "$log_path" && echo "--" && tail --lines=$length "$log_path" >&2 fi echo "" From d495d315c0e1c9492e94e0e810a0e6cdb3d4c975 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Feb 2019 16:36:35 +0100 Subject: [PATCH 223/721] [fix] Loading only one helper file leads to errors because missing getopts --- data/hooks/backup/05-conf_ldap | 2 +- data/hooks/backup/08-conf_ssh | 2 +- data/hooks/backup/11-conf_ynh_mysql | 2 +- data/hooks/backup/14-conf_ssowat | 2 +- data/hooks/backup/17-data_home | 2 +- data/hooks/backup/20-conf_ynh_firewall | 2 +- data/hooks/backup/21-conf_ynh_certs | 2 +- data/hooks/backup/23-data_mail | 2 +- data/hooks/backup/26-conf_xmpp | 2 +- data/hooks/backup/29-conf_nginx | 2 +- data/hooks/backup/32-conf_cron | 2 +- data/hooks/backup/40-conf_ynh_currenthost | 2 +- data/hooks/conf_regen/03-ssh | 2 +- data/hooks/conf_regen/15-nginx | 2 +- data/hooks/conf_regen/34-mysql | 4 +--- data/hooks/conf_regen/43-dnsmasq | 4 +--- data/hooks/restore/11-conf_ynh_mysql | 4 ++-- 17 files changed, 18 insertions(+), 22 deletions(-) diff --git a/data/hooks/backup/05-conf_ldap b/data/hooks/backup/05-conf_ldap index b21103ede..9ae22095e 100755 --- a/data/hooks/backup/05-conf_ldap +++ b/data/hooks/backup/05-conf_ldap @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/conf/ldap" diff --git a/data/hooks/backup/08-conf_ssh b/data/hooks/backup/08-conf_ssh index ae422617e..ee976080c 100755 --- a/data/hooks/backup/08-conf_ssh +++ b/data/hooks/backup/08-conf_ssh @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/conf/ssh" diff --git a/data/hooks/backup/11-conf_ynh_mysql b/data/hooks/backup/11-conf_ynh_mysql index 60bd8c017..031707337 100755 --- a/data/hooks/backup/11-conf_ynh_mysql +++ b/data/hooks/backup/11-conf_ynh_mysql @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/conf/ynh/mysql" diff --git a/data/hooks/backup/14-conf_ssowat b/data/hooks/backup/14-conf_ssowat index ca42d3369..d4db72493 100755 --- a/data/hooks/backup/14-conf_ssowat +++ b/data/hooks/backup/14-conf_ssowat @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/conf/ssowat" diff --git a/data/hooks/backup/17-data_home b/data/hooks/backup/17-data_home index f7a797b6b..af00d67e8 100755 --- a/data/hooks/backup/17-data_home +++ b/data/hooks/backup/17-data_home @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/data/home" diff --git a/data/hooks/backup/20-conf_ynh_firewall b/data/hooks/backup/20-conf_ynh_firewall index 4e08114e7..98be3eb09 100755 --- a/data/hooks/backup/20-conf_ynh_firewall +++ b/data/hooks/backup/20-conf_ynh_firewall @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/conf/ynh/firewall" diff --git a/data/hooks/backup/21-conf_ynh_certs b/data/hooks/backup/21-conf_ynh_certs index f9687164d..a3912a995 100755 --- a/data/hooks/backup/21-conf_ynh_certs +++ b/data/hooks/backup/21-conf_ynh_certs @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/conf/ynh/certs" diff --git a/data/hooks/backup/23-data_mail b/data/hooks/backup/23-data_mail index 618a0aafe..7fdc883fd 100755 --- a/data/hooks/backup/23-data_mail +++ b/data/hooks/backup/23-data_mail @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/data/mail" diff --git a/data/hooks/backup/26-conf_xmpp b/data/hooks/backup/26-conf_xmpp index 12300a00a..b55ad2bfc 100755 --- a/data/hooks/backup/26-conf_xmpp +++ b/data/hooks/backup/26-conf_xmpp @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/conf/xmpp" diff --git a/data/hooks/backup/29-conf_nginx b/data/hooks/backup/29-conf_nginx index d900c7535..81e145e24 100755 --- a/data/hooks/backup/29-conf_nginx +++ b/data/hooks/backup/29-conf_nginx @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/conf/nginx" diff --git a/data/hooks/backup/32-conf_cron b/data/hooks/backup/32-conf_cron index 2fea9f53f..063ec1a3f 100755 --- a/data/hooks/backup/32-conf_cron +++ b/data/hooks/backup/32-conf_cron @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/conf/cron" diff --git a/data/hooks/backup/40-conf_ynh_currenthost b/data/hooks/backup/40-conf_ynh_currenthost index e4a684576..6a98fd0d2 100755 --- a/data/hooks/backup/40-conf_ynh_currenthost +++ b/data/hooks/backup/40-conf_ynh_currenthost @@ -4,7 +4,7 @@ set -eu # Source YNH helpers -source /usr/share/yunohost/helpers.d/filesystem +source /usr/share/yunohost/helpers # Backup destination backup_dir="${1}/conf/ynh" diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index 9de527518..5bb9cf916 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -2,7 +2,7 @@ set -e -. /usr/share/yunohost/helpers.d/utils +. /usr/share/yunohost/helpers do_pre_regen() { pending_dir=$1 diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 461c10c0c..7ca63c003 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -2,7 +2,7 @@ set -e -. /usr/share/yunohost/helpers.d/utils +. /usr/share/yunohost/helpers do_init_regen() { if [[ $EUID -ne 0 ]]; then diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 5ee91827b..9f35fec18 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -2,6 +2,7 @@ set -e MYSQL_PKG="mariadb-server-10.1" +. /usr/share/yunohost/helpers do_pre_regen() { pending_dir=$1 @@ -15,7 +16,6 @@ do_post_regen() { regen_conf_files=$1 if [ ! -f /etc/yunohost/mysql ]; then - . /usr/share/yunohost/helpers.d/string # ensure that mysql is running sudo systemctl -q is-active mysql.service \ @@ -25,8 +25,6 @@ do_post_regen() { 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." \ diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 2c8ce797b..ed795c058 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -1,13 +1,11 @@ #!/bin/bash set -e +. /usr/share/yunohost/helpers do_pre_regen() { pending_dir=$1 - # source ip helpers - . /usr/share/yunohost/helpers.d/ip - cd /usr/share/yunohost/templates/dnsmasq # create directory for pending conf diff --git a/data/hooks/restore/11-conf_ynh_mysql b/data/hooks/restore/11-conf_ynh_mysql index 0aaaccd54..1336a2cc2 100644 --- a/data/hooks/restore/11-conf_ynh_mysql +++ b/data/hooks/restore/11-conf_ynh_mysql @@ -1,6 +1,8 @@ backup_dir="$1/conf/ynh/mysql" MYSQL_PKG="mariadb-server-10.1" +. /usr/share/yunohost/helpers + # ensure that mysql is running service mysql status >/dev/null 2>&1 \ || service mysql start @@ -11,13 +13,11 @@ service mysql status >/dev/null 2>&1 \ new_pwd=$(sudo cat "${backup_dir}/root_pwd" || sudo cat "${backup_dir}/mysql") [ -z "$curr_pwd" ] && curr_pwd="yunohost" [ -z "$new_pwd" ] && { - . /usr/share/yunohost/helpers.d/string new_pwd=$(ynh_string_random 10) } # 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" \ From 683d62d0e5df9dd07ca569e48a4c96e9ca9856d8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Feb 2019 18:43:14 +0100 Subject: [PATCH 224/721] [microdecision] Fix interface with meltdown checker script, stdout contains weird debug messages when ran inside LXC :| --- src/yunohost/tools.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a220e21ca..b2fbf380c 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -735,6 +735,14 @@ def _check_if_vulnerable_to_meltdown(): output, err = call.communicate() assert call.returncode in (0, 2, 3), "Return code: %s" % call.returncode + # If there are multiple lines, sounds like there was some messages + # in stdout that are not json >.> ... Try to get the actual json + # stuff which should be the last line + output = output.strip() + if "\n" in output: + logger.debug("Original meltdown checker output : %s" % output) + output = output.split("\n")[-1] + CVEs = json.loads(output) assert len(CVEs) == 1 assert CVEs[0]["NAME"] == "MELTDOWN" From d0c7603d9db1a406c203e97f95c405e6bb0d941a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Feb 2019 20:25:30 +0100 Subject: [PATCH 225/721] Wtf is wrong with you sudo :| Why don't you forward the damn DEBIAN_FRONTEND --- data/helpers.d/package | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/package b/data/helpers.d/package index 1b7c38c5c..e7aa3faf8 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -71,7 +71,7 @@ ynh_package_version() { # usage: ynh_apt update ynh_apt() { ynh_wait_dpkg_free - DEBIAN_FRONTEND=noninteractive sudo apt-get -y $@ + DEBIAN_FRONTEND=noninteractive apt-get -y $@ } # Update package index files @@ -150,7 +150,7 @@ ynh_package_install_from_equivs () { cp "$controlfile" "${TMPDIR}/control" (cd "$TMPDIR" equivs-build ./control 1> /dev/null - DEBIAN_FRONTEND=noninteractive dpkg --force-depends -i "./${pkgname}_${pkgversion}_all.deb" 2>&1) + dpkg --force-depends -i "./${pkgname}_${pkgversion}_all.deb" 2>&1) ynh_package_install -f || ynh_die --message="Unable to install dependencies" [[ -n "$TMPDIR" ]] && rm -rf $TMPDIR # Remove the temp dir. From fc00b59be4561ab04009c87c49b60ddfe99f9189 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Feb 2019 20:44:50 +0100 Subject: [PATCH 226/721] Add messages before app install, remove, and some messages for backups --- locales/en.json | 7 ++++++- src/yunohost/app.py | 4 ++++ src/yunohost/backup.py | 6 +++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index 3681fd847..34258187e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -35,6 +35,10 @@ "app_requirements_failed": "Unable to meet requirements for {app}: {error}", "app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}", "app_sources_fetch_failed": "Unable to fetch sources files", + "app_start_install": "Installing application {app}…", + "app_start_remove": "Removing application {app}…", + "app_start_backup": "Collecting files to be backuped for {app}…", + "app_start_restore": "Restoring application {app}…", "app_unknown": "Unknown app", "app_unsupported_remote_type": "Unsupported remote type used for the app", "app_upgrade_several_apps": "The following apps will be upgraded : {apps}", @@ -65,6 +69,7 @@ "ask_path": "Path", "backup_abstract_method": "This backup method hasn't yet been implemented", "backup_action_required": "You must specify something to save", + "backup_actually_backuping": "Now creating a backup archive from the files collected…", "backup_app_failed": "Unable to back up the app '{app:s}'", "backup_applying_method_borg": "Sending all files to backup into borg-backup repository…", "backup_applying_method_copy": "Copying all files to backup…", @@ -101,6 +106,7 @@ "backup_method_copy_finished": "Backup copy finished", "backup_method_custom_finished": "Custom backup method '{method:s}' finished", "backup_method_tar_finished": "Backup tar archive created", + "backup_mount_archive_for_restore": "Preparing archive for restoration…", "backup_no_uncompress_archive_dir": "Uncompress archive directory doesn't exist", "backup_nothings_done": "There is nothing to save", "backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", @@ -108,7 +114,6 @@ "backup_output_directory_required": "You must provide an output directory for the backup", "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.", "backup_php5_to_php7_migration_may_fail": "Could not convert your archive to support php7, your php apps may fail to restore (reason: {error:s})", - "backup_running_app_script": "Running backup script of app '{app:s}'…", "backup_running_hooks": "Running backup hooks…", "backup_system_part_failed": "Unable to backup the '{part:s}' system part", "backup_unable_to_organize_files": "Unable to organize files in the archive with the quick method", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5e7f17b6e..f5746fd4b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -801,6 +801,8 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on operation_logger.related_to.append(("app", app_id)) operation_logger.start() + logger.info(m18n.n("app_start_install", app=app_id)) + # Create app directory app_setting_path = os.path.join(APPS_SETTING_PATH, app_instance_name) if os.path.exists(app_setting_path): @@ -921,6 +923,8 @@ def app_remove(operation_logger, auth, app): operation_logger.start() + logger.info(m18n.n("app_start_remove", app=app)) + app_setting_path = APPS_SETTING_PATH + app # TODO: display fail messages from script diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index ed7799fc1..36d732114 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -668,7 +668,7 @@ class BackupManager(): tmp_app_bkp_dir = env_dict["YNH_APP_BACKUP_DIR"] settings_dir = os.path.join(self.work_dir, 'apps', app, 'settings') - logger.debug(m18n.n('backup_running_app_script', app=app)) + logger.info(m18n.n("app_start_backup", app=app)) try: # Prepare backup directory for the app filesystem.mkdir(tmp_app_bkp_dir, 0o750, True, uid='admin') @@ -1242,6 +1242,8 @@ class RestoreManager(): operation_logger = OperationLogger('backup_restore_app', related_to) operation_logger.start() + logger.info(m18n.n("app_start_restore", app=app_instance_name)) + # Check if the app is not already installed if _is_installed(app_instance_name): logger.error(m18n.n('restore_already_installed_app', @@ -2059,6 +2061,7 @@ def backup_create(name=None, description=None, methods=[], backup_manager.collect_files() # Apply backup methods on prepared files + logger.info(m18n.n("backup_actually_backuping")) backup_manager.backup() logger.success(m18n.n('backup_created')) @@ -2127,6 +2130,7 @@ def backup_restore(auth, name, system=[], apps=[], force=False): # Mount the archive then call the restore for each system part / app # # + logger.info(m18n.n("backup_mount_archive_for_restore")) restore_manager.mount() restore_manager.restore() From 9f3bf11fd06e3d16136e3de9a376c38d3179b956 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 18 Feb 2019 00:04:55 +0100 Subject: [PATCH 227/721] Add warning about --line_match usage --- data/helpers.d/system | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/helpers.d/system b/data/helpers.d/system index 71db85390..d63a4b482 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -63,6 +63,9 @@ ynh_get_debian_release () { # | arg: -a, --action= - Action to perform with systemctl. Default: start # | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. # If not defined it don't wait until the service is completely started. +# WARNING: When using --line_match, you should always add `ynh_clean_check_starting` into your +# `ynh_clean_setup` at the beginning of the script. Otherwise, tail will not stop in case of failure +# of the script. The script will then hang forever. # | arg: -p, --log_path= - Log file - Path to the log file. Default : /var/log/$app/$app.log # | arg: -t, --timeout= - Timeout - The maximum time to wait before ending the watching. Default : 300 seconds. # | arg: -e, --length= - Length of the error log : Default : 20 From 9052bb0b78acc46327ba524ac8010b8ad3fc9133 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 18 Feb 2019 00:11:04 +0100 Subject: [PATCH 228/721] Do not pipe into less --- data/helpers.d/system | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index d63a4b482..58bc77582 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -106,7 +106,7 @@ ynh_systemd_action() { echo "${action^} the service $service_name" >&2 systemctl $action $service_name \ - || ( journalctl --lines=$length -u $service_name >&2 \ + || ( journalctl --no-pager --lines=$length -u $service_name >&2 \ ; test -e "$log_path" && echo "--" && tail --lines=$length "$log_path" >&2 \ ; false ) @@ -129,7 +129,7 @@ ynh_systemd_action() { then echo "The service $service_name didn't fully started before the timeout." >&2 echo "Please find here an extract of the end of the log of the service $service_name:" - journalctl --lines=$length -u $service_name >&2 + journalctl --no-pager --lines=$length -u $service_name >&2 test -e "$log_path" && echo "--" && tail --lines=$length "$log_path" >&2 fi From 631ca4763664dde1c517fd21dbcc94abafc95599 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 18 Feb 2019 00:12:01 +0100 Subject: [PATCH 229/721] Be sure to get the correct PID --- data/helpers.d/system | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index 58bc77582..a846ee590 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -96,12 +96,14 @@ ynh_systemd_action() { if [ "$log_path" == "systemd" ] ; then # Read the systemd journal journalctl --unit=$service_name --follow --since=-0 --quiet > "$templog" & + # Get the PID of the journalctl command + local pid_tail=$! else # Read the specified log file tail -F -n0 "$log_path" > "$templog" & + # Get the PID of the tail command + local pid_tail=$! fi - # Get the PID of the tail command - local pid_tail=$! fi echo "${action^} the service $service_name" >&2 From 1d8d3c282861d3889826d0db493bedf3a2aa1f06 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 15:58:05 +0100 Subject: [PATCH 230/721] Add small helper to check if dpkg is in a broken state --- src/yunohost/utils/packages.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 5ef97618b..9cb3bd974 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -19,6 +19,7 @@ """ import re +import os import logging from collections import OrderedDict @@ -470,3 +471,11 @@ def ynh_packages_version(*args, **kwargs): 'yunohost', 'yunohost-admin', 'moulinette', 'ssowat', with_repo=True ) + + +def dpkg_is_broken(): + # If dpkg is broken, /var/lib/dpkg/updates + # will contains files like 0001, 0002, ... + # ref: https://sources.debian.org/src/apt/1.4.9/apt-pkg/deb/debsystem.cc/#L141-L174 + return any(re.match("^[0-9]+$", f) + for f in os.listdir("/var/lib/dpkg/updates/")) From 19bd4da10400497bfa8de70d22d4c105ad4e6b5a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 15:58:24 +0100 Subject: [PATCH 231/721] Assert that dpkg is not broken when trying to install an app --- locales/en.json | 1 + src/yunohost/app.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/locales/en.json b/locales/en.json index 8528c2576..477563c8b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -146,6 +146,7 @@ "diagnosis_monitor_network_error": "Can't monitor network: {error}", "diagnosis_monitor_system_error": "Can't monitor system: {error}", "diagnosis_no_apps": "No installed application", + "dpkg_is_broken": "You cannot do this right now because dpkg/apt (the system package managers) seems to be in a broken state... You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "dnsmasq_isnt_installed": "dnsmasq does not seem to be installed, please run 'apt-get remove bind9 && apt-get install dnsmasq'", "domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first", "domain_cert_gen_failed": "Unable to generate certificate", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0bca68787..d332971c4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -722,6 +722,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on }, } + if packages.dpkg_is_broken(): + raise YunohostError(m18n.n("dpkg_is_broken")) + def confirm_install(confirm): # Ignore if there's nothing for confirm (good quality app), if --force is used From 42b41aa934c8ca88c21795d4976d690c463cf9a1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Feb 2019 17:00:46 +0100 Subject: [PATCH 232/721] Add new regen-conf API in tools category --- data/actionsmap/yunohost.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index cbe959b55..fabdcb923 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1623,6 +1623,32 @@ tools: full: --force action: store_true + ### tools_regen_conf() + regen-conf: + action_help: Regenerate the configuration file(s) + api: PUT /tools/regenconf + arguments: + names: + help: Categories to regenerate configuration of (all by default) + nargs: "*" + metavar: NAME + -d: + full: --with-diff + help: Show differences in case of configuration changes + action: store_true + -f: + full: --force + help: Override all manual modifications in configuration files + action: store_true + -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 + subcategories: migrations: From 739bf8e559af5e52cc0c55bf614d649b8737c7ee Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Feb 2019 17:23:44 +0100 Subject: [PATCH 233/721] Brutally move regenconf stuff to a new regenconf.py file --- src/yunohost/regenconf.py | 546 ++++++++++++++++++++++++++++++++++++++ src/yunohost/service.py | 475 --------------------------------- 2 files changed, 546 insertions(+), 475 deletions(-) create mode 100644 src/yunohost/regenconf.py diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py new file mode 100644 index 000000000..0330db508 --- /dev/null +++ b/src/yunohost/regenconf.py @@ -0,0 +1,546 @@ +# -*- coding: utf-8 -*- + +""" License + + Copyright (C) 2019 YunoHost + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses + +""" + +import os +import yaml +import json +import subprocess +import shutil +import hashlib + +from difflib import unified_diff +from datetime import datetime + +from moulinette import m18n +from moulinette.utils import log, filesystem + +from yunohost.utils.error import YunohostError +from yunohost.log import is_unit_operation +from yunohost.hook import hook_callback, hook_list + +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.regenconf') + + +@is_unit_operation([('names', 'service')]) +def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run=False, + list_pending=False): + """ + Regenerate the configuration file(s) for a service + + Keyword argument: + 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 + + """ + result = {} + + # Return the list of pending conf + if list_pending: + pending_conf = _get_pending_conf(names) + + if not with_diff: + return pending_conf + + 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 + + if not dry_run: + operation_logger.related_to = [('service', x) for x in names] + if not names: + operation_logger.name_parameter_override = 'all' + elif len(names) != 1: + operation_logger.name_parameter_override = str(len(operation_logger.related_to)) + '_services' + operation_logger.start() + + # Clean pending conf directory + if os.path.isdir(PENDING_CONF_DIR): + if not names: + shutil.rmtree(PENDING_CONF_DIR, ignore_errors=True) + else: + for name in names: + shutil.rmtree(os.path.join(PENDING_CONF_DIR, name), + ignore_errors=True) + else: + filesystem.mkdir(PENDING_CONF_DIR, 0o755, 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, 0o755, True, uid='root') + + # return the arguments to pass to the script + return pre_args + [service_pending_path, ] + + # Don't regen SSH if not specifically specified + if not names: + names = hook_list('conf_regen', list_by='name', + show_info=False)['hooks'] + names.remove('ssh') + + pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) + + # Update the services name + names = pre_result['succeed'].keys() + + if not names: + raise YunohostError('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 + + operation_logger.related_to = [] + + # Iterate over services and process pending conf + for service, conf_files in _get_pending_conf(names).items(): + if not dry_run: + operation_logger.related_to.append(('service', service)) + + logger.debug(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 + system_hash = _calculate_hash(system_path) + saved_hash = conf_hashes.get(system_path, None) + 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 saved_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.info(m18n.n( + 'service_conf_file_manually_removed', + conf=system_path)) + conf_status = 'removed' + + # -> system conf is not managed yet + elif not saved_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 not to_remove: + # If the conf exist but is not managed yet, and is not to be removed, + # we assume that it is safe to regen it, since the file is backuped + # anyway (by default in _regen), as long as we warn the user + # appropriately. + logger.info(m18n.n('service_conf_now_managed_by_yunohost', + conf=system_path)) + regenerated = _regen(system_path, pending_path) + conf_status = 'new' + elif force: + regenerated = _regen(system_path) + conf_status = 'force-removed' + else: + logger.info(m18n.n('service_conf_file_kept_back', + conf=system_path, service=service)) + conf_status = 'unmanaged' + + # -> system conf has not been manually modified + elif system_hash == saved_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 system_hash == new_hash: + logger.debug("> new conf is as current system conf") + conf_status = 'managed' + regenerated = True + elif 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.debug(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) + + operation_logger.success() + + return result + + +def _get_services(): + """ + Get a dict of managed services with their parameters + + """ + try: + with open('/etc/yunohost/services.yml', 'r') as f: + services = yaml.load(f) + except: + return {} + else: + # some services are marked as None to remove them from YunoHost + # filter this + for key, value in services.items(): + if value is None: + del services[key] + + return services + + +def _save_services(services): + """ + Save managed services to files + + Keyword argument: + services -- A dict of managed services with their parameters + + """ + try: + with open('/etc/yunohost/services.yml', 'w') as f: + yaml.safe_dump(services, f, default_flow_style=False) + except Exception as e: + logger.warning('Error while saving services, exception: %s', e, exc_info=1) + raise + + +def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True): + """Compare two files and return the differences + + 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. + + """ + + if os.path.exists(orig_file): + with open(orig_file, 'r') as orig_file: + orig_file = orig_file.readlines() + else: + orig_file = [] + + if os.path.exists(new_file): + with open(new_file, 'r') as new_file: + new_file = new_file.readlines() + else: + new_file = [] + + # Compare files and format output + diff = unified_diff(orig_file, new_file) + + if skip_header: + try: + next(diff) + next(diff) + except: + pass + + if as_string: + return ''.join(diff).rstrip() + + return diff + + +def _calculate_hash(path): + """Calculate the MD5 hash of a file""" + + if not os.path.exists(path): + return None + + hasher = hashlib.md5() + + try: + with open(path, 'rb') as f: + hasher.update(f.read()) + return hasher.hexdigest() + + except IOError as e: + logger.warning("Error while calculating file '%s' hash: %s", path, e, exc_info=1) + return None + + +def _get_pending_conf(services=[]): + """Get pending configuration for service(s) + + 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. + + """ + 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_pending_path = os.path.join(PENDING_CONF_DIR, name) + + if not os.path.isdir(service_pending_path): + continue + + path_index = len(service_pending_path) + service_conf = {} + + 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 + else: + # remove empty directory + shutil.rmtree(service_pending_path, ignore_errors=True) + + return result + + +def _get_conf_hashes(service): + """Get the registered conf hashes for a service""" + + services = _get_services() + + if service not in services: + logger.debug("Service %s is not in services.yml yet.", service) + return {} + + elif services[service] is None or 'conffiles' not in services[service]: + logger.debug("No configuration files for service %s.", service) + return {} + + else: + return services[service]['conffiles'] + + +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() + service_conf = services.get(service, {}) + + # Handle the case where services[service] is set to null in the yaml + if service_conf is None: + service_conf = {} + + service_conf['conffiles'] = hashes + services[service] = service_conf + _save_services(services) + + +def _process_regen_conf(system_conf, new_conf=None, save=True): + """Regenerate a given system configuration file + + 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. + + """ + if save: + backup_path = os.path.join(BACKUP_CONF_DIR, '{0}-{1}'.format( + system_conf.lstrip('/'), datetime.utcnow().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, 0o755, True) + + shutil.copy2(system_conf, backup_path) + logger.debug(m18n.n('service_conf_file_backed_up', + conf=system_conf, backup=backup_path)) + + try: + if not new_conf: + os.remove(system_conf) + logger.debug(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, 0o755, True) + + shutil.copyfile(new_conf, system_conf) + logger.debug(m18n.n('service_conf_file_updated', + conf=system_conf)) + except Exception as e: + logger.warning("Exception while trying to regenerate conf '%s': %s", system_conf, e, exc_info=1) + 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: + # From documentation: + # Raise an exception if an os.stat() call on either pathname fails. + # (os.stats returns a series of information from a file like type, size...) + 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 + + +def manually_modified_files(): + + # We do this to have --quiet, i.e. don't throw a whole bunch of logs + # just to fetch this... + # Might be able to optimize this by looking at what service_regenconf does + # and only do the part that checks file hashes... + cmd = "yunohost service regen-conf --dry-run --output-as json --quiet" + j = json.loads(subprocess.check_output(cmd.split())) + + # j is something like : + # {"postfix": {"applied": {}, "pending": {"/etc/postfix/main.cf": {"status": "modified"}}} + + output = [] + for app, actions in j.items(): + for action, files in actions.items(): + for filename, infos in files.items(): + if infos["status"] == "modified": + output.append(filename) + + return output + + +def manually_modified_files_compared_to_debian_default(): + + # from https://serverfault.com/a/90401 + r = subprocess.check_output("dpkg-query -W -f='${Conffiles}\n' '*' \ + | awk 'OFS=\" \"{print $2,$1}' \ + | md5sum -c 2>/dev/null \ + | awk -F': ' '$2 !~ /OK/{print $1}'", shell=True) + return r.strip().split("\n") diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 60729053b..ab0e791d8 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -26,12 +26,8 @@ import os import time import yaml -import json import subprocess -import shutil -import hashlib -from difflib import unified_diff from datetime import datetime from moulinette import m18n @@ -39,11 +35,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils import log, filesystem from yunohost.log import is_unit_operation -from yunohost.hook import hook_callback, hook_list -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') MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" logger = log.getActionLogger('yunohost.service') @@ -418,251 +410,6 @@ def service_log(name, number=50): return result -@is_unit_operation([('names', 'service')]) -def service_regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run=False, - list_pending=False): - """ - Regenerate the configuration file(s) for a service - - Keyword argument: - 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 - - """ - result = {} - - # Return the list of pending conf - if list_pending: - pending_conf = _get_pending_conf(names) - - if not with_diff: - return pending_conf - - 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 - - if not dry_run: - operation_logger.related_to = [('service', x) for x in names] - if not names: - operation_logger.name_parameter_override = 'all' - elif len(names) != 1: - operation_logger.name_parameter_override = str(len(operation_logger.related_to)) + '_services' - operation_logger.start() - - # Clean pending conf directory - if os.path.isdir(PENDING_CONF_DIR): - if not names: - shutil.rmtree(PENDING_CONF_DIR, ignore_errors=True) - else: - for name in names: - shutil.rmtree(os.path.join(PENDING_CONF_DIR, name), - ignore_errors=True) - else: - filesystem.mkdir(PENDING_CONF_DIR, 0o755, 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, 0o755, True, uid='root') - - # return the arguments to pass to the script - return pre_args + [service_pending_path, ] - - # Don't regen SSH if not specifically specified - if not names: - names = hook_list('conf_regen', list_by='name', - show_info=False)['hooks'] - names.remove('ssh') - - pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) - - # Update the services name - names = pre_result['succeed'].keys() - - if not names: - raise YunohostError('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 - - operation_logger.related_to = [] - - # Iterate over services and process pending conf - for service, conf_files in _get_pending_conf(names).items(): - if not dry_run: - operation_logger.related_to.append(('service', service)) - - logger.debug(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 - system_hash = _calculate_hash(system_path) - saved_hash = conf_hashes.get(system_path, None) - 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 saved_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.info(m18n.n( - 'service_conf_file_manually_removed', - conf=system_path)) - conf_status = 'removed' - - # -> system conf is not managed yet - elif not saved_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 not to_remove: - # If the conf exist but is not managed yet, and is not to be removed, - # we assume that it is safe to regen it, since the file is backuped - # anyway (by default in _regen), as long as we warn the user - # appropriately. - logger.info(m18n.n('service_conf_now_managed_by_yunohost', - conf=system_path)) - regenerated = _regen(system_path, pending_path) - conf_status = 'new' - elif force: - regenerated = _regen(system_path) - conf_status = 'force-removed' - else: - logger.info(m18n.n('service_conf_file_kept_back', - conf=system_path, service=service)) - conf_status = 'unmanaged' - - # -> system conf has not been manually modified - elif system_hash == saved_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 system_hash == new_hash: - logger.debug("> new conf is as current system conf") - conf_status = 'managed' - regenerated = True - elif 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.debug(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) - - operation_logger.success() - - return result - - def _run_service_command(action, service): """ Run services management command (start, stop, enable, disable, restart, reload) @@ -860,231 +607,9 @@ def _find_previous_log_file(file): return None -def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True): - """Compare two files and return the differences - - 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. - - """ - - if os.path.exists(orig_file): - with open(orig_file, 'r') as orig_file: - orig_file = orig_file.readlines() - else: - orig_file = [] - - if os.path.exists(new_file): - with open(new_file, 'r') as new_file: - new_file = new_file.readlines() - else: - new_file = [] - - # Compare files and format output - diff = unified_diff(orig_file, new_file) - - if skip_header: - try: - next(diff) - next(diff) - except: - pass - - if as_string: - return ''.join(diff).rstrip() - - return diff - - -def _calculate_hash(path): - """Calculate the MD5 hash of a file""" - - if not os.path.exists(path): - return None - - hasher = hashlib.md5() - - try: - with open(path, 'rb') as f: - hasher.update(f.read()) - return hasher.hexdigest() - - except IOError as e: - logger.warning("Error while calculating file '%s' hash: %s", path, e, exc_info=1) - return None - - -def _get_pending_conf(services=[]): - """Get pending configuration for service(s) - - 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. - - """ - 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_pending_path = os.path.join(PENDING_CONF_DIR, name) - - if not os.path.isdir(service_pending_path): - continue - - path_index = len(service_pending_path) - service_conf = {} - - 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 - else: - # remove empty directory - shutil.rmtree(service_pending_path, ignore_errors=True) - - return result - - -def _get_conf_hashes(service): - """Get the registered conf hashes for a service""" - - services = _get_services() - - if service not in services: - logger.debug("Service %s is not in services.yml yet.", service) - return {} - - elif services[service] is None or 'conffiles' not in services[service]: - logger.debug("No configuration files for service %s.", service) - return {} - - else: - return services[service]['conffiles'] - - -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() - service_conf = services.get(service, {}) - - # Handle the case where services[service] is set to null in the yaml - if service_conf is None: - service_conf = {} - - service_conf['conffiles'] = hashes - services[service] = service_conf - _save_services(services) - - -def _process_regen_conf(system_conf, new_conf=None, save=True): - """Regenerate a given system configuration file - - 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. - - """ - if save: - backup_path = os.path.join(BACKUP_CONF_DIR, '{0}-{1}'.format( - system_conf.lstrip('/'), datetime.utcnow().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, 0o755, True) - - shutil.copy2(system_conf, backup_path) - logger.debug(m18n.n('service_conf_file_backed_up', - conf=system_conf, backup=backup_path)) - - try: - if not new_conf: - os.remove(system_conf) - logger.debug(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, 0o755, True) - - shutil.copyfile(new_conf, system_conf) - logger.debug(m18n.n('service_conf_file_updated', - conf=system_conf)) - except Exception as e: - logger.warning("Exception while trying to regenerate conf '%s': %s", system_conf, e, exc_info=1) - 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: - # From documentation: - # Raise an exception if an os.stat() call on either pathname fails. - # (os.stats returns a series of information from a file like type, size...) - 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 - - -def manually_modified_files(): - - # We do this to have --quiet, i.e. don't throw a whole bunch of logs - # just to fetch this... - # Might be able to optimize this by looking at what service_regenconf does - # and only do the part that checks file hashes... - cmd = "yunohost service regen-conf --dry-run --output-as json --quiet" - j = json.loads(subprocess.check_output(cmd.split())) - - # j is something like : - # {"postfix": {"applied": {}, "pending": {"/etc/postfix/main.cf": {"status": "modified"}}} - - output = [] - for app, actions in j.items(): - for action, files in actions.items(): - for filename, infos in files.items(): - if infos["status"] == "modified": - output.append(filename) - - return output - - def _get_journalctl_logs(service, number="all"): try: return subprocess.check_output("journalctl -xn -u {0} -n{1}".format(service, number), shell=True) except: import traceback return "error while get services logs from journalctl:\n%s" % traceback.format_exc() - - -def manually_modified_files_compared_to_debian_default(): - - # from https://serverfault.com/a/90401 - r = subprocess.check_output("dpkg-query -W -f='${Conffiles}\n' '*' \ - | awk 'OFS=\" \"{print $2,$1}' \ - | md5sum -c 2>/dev/null \ - | awk -F': ' '$2 !~ /OK/{print $1}'", shell=True) - return r.strip().split("\n") From 3067e8e8975e77a3309a3038e0ba614647a381b6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Feb 2019 12:03:17 +0100 Subject: [PATCH 234/721] Misc renaming for consistency, basically service->category --- locales/en.json | 30 ++++---- src/yunohost/regenconf.py | 152 +++++++++++++++++++------------------- 2 files changed, 92 insertions(+), 90 deletions(-) diff --git a/locales/en.json b/locales/en.json index 8528c2576..34517a036 100644 --- a/locales/en.json +++ b/locales/en.json @@ -377,6 +377,21 @@ "port_available": "Port {port:d} is available", "port_unavailable": "Port {port:d} is not available", "recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create' or the admin interface.", + "regenconf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", + "regenconf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", + "regenconf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by regen-conf (category {category}) but has been kept back.", + "regenconf_file_manually_modified": "The configuration file '{conf}' has been manually modified and will not be updated", + "regenconf_file_manually_removed": "The configuration file '{conf}' has been manually removed and will not be created", + "regenconf_file_remove_failed": "Unable to remove the configuration file '{conf}'", + "regenconf_file_removed": "The configuration file '{conf}' has been removed", + "regenconf_file_updated": "The configuration file '{conf}' has been updated", + "regenconf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost (category {category}).", + "regenconf_up_to_date": "The configuration is already up-to-date for category '{category}'", + "regenconf_updated": "The configuration has been updated for category '{category}'", + "regenconf_would_be_updated": "The configuration would have been updated for category '{category}'", + "regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for category '{category}'…", + "regenconf_failed": "Unable to regenerate the configuration for category(s): {categories}", + "regenconf_pending_applying": "Applying pending configuration for category '{category}'…", "restore_action_required": "You must specify something to restore", "restore_already_installed_app": "An app is already installed with the id '{app:s}'", "restore_app_failed": "Unable to restore the app '{app:s}'", @@ -405,18 +420,6 @@ "service_already_started": "Service '{service:s}' has already been started", "service_already_stopped": "Service '{service:s}' has already been stopped", "service_cmd_exec_failed": "Unable to execute command '{command:s}'", - "service_conf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'", - "service_conf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'", - "service_conf_file_kept_back": "The configuration file '{conf}' is expected to be deleted by service {service} but has been kept back.", - "service_conf_file_manually_modified": "The configuration file '{conf}' has been manually modified and will not be updated", - "service_conf_file_manually_removed": "The configuration file '{conf}' has been manually removed and will not be created", - "service_conf_file_remove_failed": "Unable to remove the configuration file '{conf}'", - "service_conf_file_removed": "The configuration file '{conf}' has been removed", - "service_conf_file_updated": "The configuration file '{conf}' has been updated", - "service_conf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost.", - "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}'", "service_description_avahi-daemon": "allows to reach your server using yunohost.local on your local network", "service_description_dnsmasq": "handles domain name resolution (DNS)", "service_description_dovecot": "allows e-mail client to access/fetch email (via IMAP and POP3)", @@ -440,9 +443,6 @@ "service_enable_failed": "Unable to enable service '{service:s}'\n\nRecent service logs:{logs:s}", "service_enabled": "The service '{service:s}' has been enabled", "service_no_log": "No log to display for service '{service:s}'", - "service_regenconf_dry_pending_applying": "Checking pending configuration which would have been applied for service '{service}'…", - "service_regenconf_failed": "Unable to regenerate the configuration for service(s): {services}", - "service_regenconf_pending_applying": "Applying pending configuration for service '{service}'…", "service_remove_failed": "Unable to remove service '{service:s}'", "service_removed": "The service '{service:s}' has been removed", "service_reload_failed": "Unable to reload service '{service:s}'\n\nRecent service logs:{logs:s}", diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 0330db508..4104e2491 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -43,14 +43,16 @@ PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending') logger = log.getActionLogger('yunohost.regenconf') +# FIXME : those ain't just services anymore ... what are we supposed to do with this ... +# FIXME : check for all reference of 'service' close to operation_logger stuff @is_unit_operation([('names', 'service')]) def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run=False, list_pending=False): """ - Regenerate the configuration file(s) for a service + Regenerate the configuration file(s) Keyword argument: - names -- Services name to regenerate configuration of + names -- Categories 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 @@ -66,10 +68,10 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run if not with_diff: return pending_conf - for service, conf_files in pending_conf.items(): + for category, conf_files in pending_conf.items(): for system_path, pending_path in conf_files.items(): - pending_conf[service][system_path] = { + pending_conf[category][system_path] = { 'pending_conf': pending_path, 'diff': _get_files_diff( system_path, pending_path, True), @@ -103,12 +105,12 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run 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, 0o755, True, uid='root') + # create the pending conf directory for the category + category_pending_path = os.path.join(PENDING_CONF_DIR, name) + filesystem.mkdir(category_pending_path, 0o755, True, uid='root') # return the arguments to pass to the script - return pre_args + [service_pending_path, ] + return pre_args + [category_pending_path, ] # Don't regen SSH if not specifically specified if not names: @@ -118,29 +120,29 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) - # Update the services name + # Update the categorys name names = pre_result['succeed'].keys() if not names: - raise YunohostError('service_regenconf_failed', - services=', '.join(pre_result['failed'])) + raise YunohostError('regenconf_failed', + categories=', '.join(pre_result['failed'])) # Set the processing method _regen = _process_regen_conf if not dry_run else lambda *a, **k: True operation_logger.related_to = [] - # Iterate over services and process pending conf - for service, conf_files in _get_pending_conf(names).items(): + # Iterate over categorys and process pending conf + for category, conf_files in _get_pending_conf(names).items(): if not dry_run: - operation_logger.related_to.append(('service', service)) + operation_logger.related_to.append(('service', category)) logger.debug(m18n.n( - 'service_regenconf_pending_applying' if not dry_run else - 'service_regenconf_dry_pending_applying', - service=service)) + 'regenconf_pending_applying' if not dry_run else + 'regenconf_dry_pending_applying', + category=category)) - conf_hashes = _get_conf_hashes(service) + conf_hashes = _get_conf_hashes(category) succeed_regen = {} failed_regen = {} @@ -179,7 +181,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run system_path, pending_path, save=False) else: logger.info(m18n.n( - 'service_conf_file_manually_removed', + 'regenconf_file_manually_removed', conf=system_path)) conf_status = 'removed' @@ -195,16 +197,16 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run # we assume that it is safe to regen it, since the file is backuped # anyway (by default in _regen), as long as we warn the user # appropriately. - logger.info(m18n.n('service_conf_now_managed_by_yunohost', - conf=system_path)) + logger.info(m18n.n('regenconf_now_managed_by_yunohost', + conf=system_path, category=category)) regenerated = _regen(system_path, pending_path) conf_status = 'new' elif force: regenerated = _regen(system_path) conf_status = 'force-removed' else: - logger.info(m18n.n('service_conf_file_kept_back', - conf=system_path, service=service)) + logger.info(m18n.n('regenconf_file_kept_back', + conf=system_path, category=category)) conf_status = 'unmanaged' # -> system conf has not been manually modified @@ -231,7 +233,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run conf_status = 'force-updated' else: logger.warning(m18n.n( - 'service_conf_file_manually_modified', + 'regenconf_file_manually_modified', conf=system_path)) conf_status = 'modified' @@ -247,21 +249,21 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run else: failed_regen[system_path] = conf_result - # Check for service conf changes + # Check for category conf changes if not succeed_regen and not failed_regen: - logger.debug(m18n.n('service_conf_up_to_date', service=service)) + logger.debug(m18n.n('regenconf_up_to_date', category=category)) continue elif not failed_regen: logger.success(m18n.n( - 'service_conf_updated' if not dry_run else - 'service_conf_would_be_updated', - service=service)) + 'regenconf_updated' if not dry_run else + 'regenconf_would_be_updated', + category=category)) if succeed_regen and not dry_run: - _update_conf_hashes(service, conf_hashes) + _update_conf_hashes(category, conf_hashes) - # Append the service results - result[service] = { + # Append the category results + result[category] = { 'applied': succeed_regen, 'pending': failed_regen } @@ -274,7 +276,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run post_args = ['post', ] + common_args def _pre_call(name, priority, path, args): - # append coma-separated applied changes for the service + # append coma-separated applied changes for the category if name in result and result[name]['applied']: regen_conf_files = ','.join(result[name]['applied'].keys()) else: @@ -379,14 +381,14 @@ def _calculate_hash(path): return None -def _get_pending_conf(services=[]): - """Get pending configuration for service(s) +def _get_pending_conf(categories=[]): + """Get pending configuration for categories - Iterate over the pending configuration directory for given service(s) - or + Iterate over the pending configuration directory for given categories - 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 + The result is returned as a dict of categories with pending configuration as key and a dict of `system_conf_path` => `pending_conf_path` as value. """ @@ -395,63 +397,63 @@ def _get_pending_conf(services=[]): if not os.path.isdir(PENDING_CONF_DIR): return result - if not services: - services = os.listdir(PENDING_CONF_DIR) + if not categories: + categories = os.listdir(PENDING_CONF_DIR) - for name in services: - service_pending_path = os.path.join(PENDING_CONF_DIR, name) + for name in categories: + category_pending_path = os.path.join(PENDING_CONF_DIR, name) - if not os.path.isdir(service_pending_path): + if not os.path.isdir(category_pending_path): continue - path_index = len(service_pending_path) - service_conf = {} + path_index = len(category_pending_path) + category_conf = {} - for root, dirs, files in os.walk(service_pending_path): + for root, dirs, files in os.walk(category_pending_path): for filename in files: pending_path = os.path.join(root, filename) - service_conf[pending_path[path_index:]] = pending_path + category_conf[pending_path[path_index:]] = pending_path - if service_conf: - result[name] = service_conf + if category_conf: + result[name] = category_conf else: # remove empty directory - shutil.rmtree(service_pending_path, ignore_errors=True) + shutil.rmtree(category_pending_path, ignore_errors=True) return result -def _get_conf_hashes(service): - """Get the registered conf hashes for a service""" +def _get_conf_hashes(category): + """Get the registered conf hashes for a category""" - services = _get_services() + categories = _get_categories() - if service not in services: - logger.debug("Service %s is not in services.yml yet.", service) + if category not in categories: + logger.debug("category %s is not in categories.yml yet.", category) return {} - elif services[service] is None or 'conffiles' not in services[service]: - logger.debug("No configuration files for service %s.", service) + elif categories[category] is None or 'conffiles' not in categories[category]: + logger.debug("No configuration files for category %s.", category) return {} else: - return services[service]['conffiles'] + return categories[category]['conffiles'] -def _update_conf_hashes(service, hashes): - """Update the registered conf hashes for a service""" +def _update_conf_hashes(category, hashes): + """Update the registered conf hashes for a category""" logger.debug("updating conf hashes for '%s' with: %s", - service, hashes) - services = _get_services() - service_conf = services.get(service, {}) + category, hashes) + categories = _get_categories() + category_conf = categories.get(category, {}) - # Handle the case where services[service] is set to null in the yaml - if service_conf is None: - service_conf = {} + # Handle the case where categories[category] is set to null in the yaml + if category_conf is None: + category_conf = {} - service_conf['conffiles'] = hashes - services[service] = service_conf - _save_services(services) + category_conf['conffiles'] = hashes + categories[category] = category_conf + _save_categories(categories) def _process_regen_conf(system_conf, new_conf=None, save=True): @@ -471,13 +473,13 @@ def _process_regen_conf(system_conf, new_conf=None, save=True): filesystem.mkdir(backup_dir, 0o755, True) shutil.copy2(system_conf, backup_path) - logger.debug(m18n.n('service_conf_file_backed_up', + logger.debug(m18n.n('regenconf_file_backed_up', conf=system_conf, backup=backup_path)) try: if not new_conf: os.remove(system_conf) - logger.debug(m18n.n('service_conf_file_removed', + logger.debug(m18n.n('regenconf_file_removed', conf=system_conf)) else: system_dir = os.path.dirname(system_conf) @@ -486,12 +488,12 @@ def _process_regen_conf(system_conf, new_conf=None, save=True): filesystem.mkdir(system_dir, 0o755, True) shutil.copyfile(new_conf, system_conf) - logger.debug(m18n.n('service_conf_file_updated', + logger.debug(m18n.n('regenconf_file_updated', conf=system_conf)) except Exception as e: logger.warning("Exception while trying to regenerate conf '%s': %s", system_conf, e, exc_info=1) if not new_conf and os.path.exists(system_conf): - logger.warning(m18n.n('service_conf_file_remove_failed', + logger.warning(m18n.n('regenconf_file_remove_failed', conf=system_conf), exc_info=1) return False @@ -506,7 +508,7 @@ def _process_regen_conf(system_conf, new_conf=None, save=True): copy_succeed = False finally: if not copy_succeed: - logger.warning(m18n.n('service_conf_file_copy_failed', + logger.warning(m18n.n('regenconf_file_copy_failed', conf=system_conf, new=new_conf), exc_info=1) return False @@ -518,9 +520,9 @@ def manually_modified_files(): # We do this to have --quiet, i.e. don't throw a whole bunch of logs # just to fetch this... - # Might be able to optimize this by looking at what service_regenconf does + # Might be able to optimize this by looking at what the regen conf does # and only do the part that checks file hashes... - cmd = "yunohost service regen-conf --dry-run --output-as json --quiet" + cmd = "yunohost tools regen-conf --dry-run --output-as json --quiet" j = json.loads(subprocess.check_output(cmd.split())) # j is something like : From e6f3a99269927a17829977dfa9682eb22827eae6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 17:44:09 +0100 Subject: [PATCH 235/721] Change binding from service_regen_conf to tools_regen_conf --- debian/postinst | 2 +- locales/en.json | 1 + src/yunohost/backup.py | 4 ++-- src/yunohost/certificate.py | 7 +++--- ...0007_ssh_conf_managed_by_yunohost_step1.py | 6 ++--- ...0008_ssh_conf_managed_by_yunohost_step2.py | 6 ++--- src/yunohost/domain.py | 6 ++--- src/yunohost/service.py | 13 +++++++++++ src/yunohost/tools.py | 22 ++++++++++++------- 9 files changed, 44 insertions(+), 23 deletions(-) diff --git a/debian/postinst b/debian/postinst index df7112b9d..83220ae0b 100644 --- a/debian/postinst +++ b/debian/postinst @@ -12,7 +12,7 @@ do_configure() { bash /usr/share/yunohost/hooks/conf_regen/15-nginx init else echo "Regenerating configuration, this might take a while..." - yunohost service regen-conf --output-as none + yunohost tools regen-conf --output-as none echo "Launching migrations.." yunohost tools migrations migrate --auto diff --git a/locales/en.json b/locales/en.json index 34517a036..5ab2a749b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -443,6 +443,7 @@ "service_enable_failed": "Unable to enable service '{service:s}'\n\nRecent service logs:{logs:s}", "service_enabled": "The service '{service:s}' has been enabled", "service_no_log": "No log to display for service '{service:s}'", + "service_regen_conf_is_deprecated": "'yunohost service regen-conf' is depracted! Please use 'yunohost tools regen-conf' instead.", "service_remove_failed": "Unable to remove service '{service:s}'", "service_removed": "The service '{service:s}' has been removed", "service_reload_failed": "Unable to reload service '{service:s}'\n\nRecent service logs:{logs:s}", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index ed7799fc1..9f48700a2 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -50,7 +50,7 @@ from yunohost.hook import ( ) from yunohost.monitor import binary_to_human from yunohost.tools import tools_postinstall -from yunohost.service import service_regen_conf +from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger from functools import reduce @@ -1191,7 +1191,7 @@ class RestoreManager(): else: operation_logger.success() - service_regen_conf() + regen_conf() def _restore_apps(self): """Restore all apps targeted""" diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 855910b8a..d7e8c0157 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -43,7 +43,8 @@ from yunohost.utils.network import get_public_ip from moulinette import m18n from yunohost.app import app_ssowatconf -from yunohost.service import _run_service_command, service_regen_conf +from yunohost.service import _run_service_command +from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger logger = getActionLogger('yunohost.certmanager') @@ -806,7 +807,7 @@ def _enable_certificate(domain, new_cert_folder): if os.path.isfile('/etc/yunohost/installed'): # regen nginx conf to be sure it integrates OCSP Stapling # (We don't do this yet if postinstall is not finished yet) - service_regen_conf(names=['nginx']) + regen_conf(names=['nginx']) _run_service_command("reload", "nginx") @@ -924,7 +925,7 @@ def _regen_dnsmasq_if_needed(): break if do_regen: - service_regen_conf(["dnsmasq"]) + regen_conf(["dnsmasq"]) def _name_self_CA(): diff --git a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py index 080cc0163..39a7d34e3 100644 --- a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py +++ b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py @@ -8,10 +8,10 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import mkdir, rm from yunohost.tools import Migration -from yunohost.service import service_regen_conf, \ - _get_conf_hashes, \ +from yunohost.service import _get_conf_hashes, \ _calculate_hash, \ _run_service_command +from yunohost.regen_conf import regen_conf from yunohost.settings import settings_set from yunohost.utils.error import YunohostError @@ -64,7 +64,7 @@ class MyMigration(Migration): if os.path.exists('/etc/yunohost/from_script'): rm('/etc/yunohost/from_script') copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp') - service_regen_conf(names=['ssh'], force=True) + regen_conf(names=['ssh'], force=True) copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF) # Restart ssh and backward if it fail diff --git a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py index 0976f1354..fce44298d 100644 --- a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py +++ b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py @@ -6,9 +6,9 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import chown from yunohost.tools import Migration -from yunohost.service import service_regen_conf, \ - _get_conf_hashes, \ +from yunohost.service import _get_conf_hashes, \ _calculate_hash +from yunohost.regenconf import regen_conf from yunohost.settings import settings_set, settings_get from yunohost.utils.error import YunohostError from yunohost.backup import ARCHIVES_PATH @@ -36,7 +36,7 @@ class MyMigration(Migration): def migrate(self): settings_set("service.ssh.allow_deprecated_dsa_hostkey", False) - service_regen_conf(names=['ssh'], force=True) + regen_conf(names=['ssh'], force=True) # Update local archives folder permissions, so that # admin can scp archives out of the server diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 3d46691f8..54ed81dfb 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -34,7 +34,7 @@ from moulinette.utils.log import getActionLogger import yunohost.certificate -from yunohost.service import service_regen_conf +from yunohost.regenconf import regen_conf from yunohost.utils.network import get_public_ip from yunohost.log import is_unit_operation @@ -111,7 +111,7 @@ def domain_add(operation_logger, auth, domain, dyndns=False): # Don't regen these conf if we're still in postinstall if os.path.exists('/etc/yunohost/installed'): - service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix', 'rspamd']) + regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix', 'rspamd']) app_ssowatconf(auth) except Exception: @@ -164,7 +164,7 @@ def domain_remove(operation_logger, auth, domain, force=False): else: raise YunohostError('domain_deletion_failed') - service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix']) + regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix']) app_ssowatconf(auth) hook_callback('post_domain_remove', args=[domain]) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index ab0e791d8..d088f0029 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -410,6 +410,19 @@ def service_log(name, number=50): return result +def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False, + list_pending=False): + + services = _get_services() + for name in names: + if name not in services: + raise YunohostError('service_unknown', service=service) + + logger.warning(m18n.n("service_regen_conf_is_deprecated")) + + return regen_conf(names, with_diff, force, dry_run, list_pending) + + def _run_service_command(action, service): """ Run services management command (start, stop, enable, disable, restart, reload) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index b2fbf380c..deec0746f 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -47,7 +47,8 @@ from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, a from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.firewall import firewall_upnp -from yunohost.service import service_status, service_regen_conf, service_log, service_start, service_enable +from yunohost.service import service_status, service_log, service_start, service_enable +from yunohost.regenconf import regen_conf from yunohost.monitor import monitor_disk, monitor_system from yunohost.utils.packages import ynh_packages_version from yunohost.utils.network import get_public_ip @@ -207,7 +208,7 @@ def tools_maindomain(operation_logger, auth, new_domain=None): # Regen configurations try: with open('/etc/yunohost/installed', 'r'): - service_regen_conf() + regen_conf() except IOError: pass @@ -325,7 +326,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, operation_logger.start() logger.info(m18n.n('yunohost_installing')) - service_regen_conf(['nslcd', 'nsswitch'], force=True) + regen_conf(['nslcd', 'nsswitch'], force=True) # Initialize LDAP for YunoHost # TODO: Improve this part by integrate ldapinit into conf_regen hook @@ -376,7 +377,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, os.system('chmod 644 /etc/ssowat/conf.json.persistent') # Create SSL CA - service_regen_conf(['ssl'], force=True) + regen_conf(['ssl'], force=True) ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' # (Update the serial so that it's specific to this very instance) os.system("openssl rand -hex 19 > %s/serial" % ssl_dir) @@ -405,7 +406,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, logger.success(m18n.n('yunohost_ca_creation_success')) # New domain config - service_regen_conf(['nsswitch'], force=True) + regen_conf(['nsswitch'], force=True) domain_add(auth, domain, dyndns) tools_maindomain(auth, domain) @@ -433,7 +434,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, service_enable("yunohost-firewall") service_start("yunohost-firewall") - service_regen_conf(force=True) + regen_conf(force=True) # Restore original ssh conf, as chosen by the # admin during the initial install @@ -450,13 +451,18 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, else: # We need to explicitly ask the regen conf to regen ssh # (by default, i.e. first argument = None, it won't because it's too touchy) - service_regen_conf(names=["ssh"], force=True) + regen_conf(names=["ssh"], force=True) logger.success(m18n.n('yunohost_configured')) logger.warning(m18n.n('recommend_to_add_first_user')) +def tools_regen_conf(names=[], with_diff=False, force=False, dry_run=False, + list_pending=False): + return regen_conf(names, with_diff, force, dry_run, list_pending) + + def tools_update(ignore_apps=False, ignore_packages=False): """ Update apps & package cache, then display changelog @@ -693,7 +699,7 @@ def tools_diagnosis(auth, private=False): # Domains diagnosis['private']['domains'] = domain_list(auth)['domains'] - diagnosis['private']['regen_conf'] = service_regen_conf(with_diff=True, dry_run=True) + diagnosis['private']['regen_conf'] = regen_conf(with_diff=True, dry_run=True) try: diagnosis['security'] = { From 39891b228c393986101cdbf5093eaa9f1f2227c4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 17:54:24 +0100 Subject: [PATCH 236/721] Inteface with regenconf.yml instead of services.yml --- src/yunohost/regenconf.py | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 4104e2491..783b50c4a 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -39,6 +39,7 @@ from yunohost.hook import hook_callback, hook_list 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') +REGEN_CONF_FILE = '/etc/yunohost/regenconf.yml' logger = log.getActionLogger('yunohost.regenconf') @@ -290,39 +291,28 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run return result -def _get_services(): +def _get_regenconf_infos(): """ - Get a dict of managed services with their parameters - + Get a dict of regen conf informations """ try: - with open('/etc/yunohost/services.yml', 'r') as f: - services = yaml.load(f) + with open(REGEN_CONF_FILE, 'r') as f: + return yaml.load(f) except: return {} - else: - # some services are marked as None to remove them from YunoHost - # filter this - for key, value in services.items(): - if value is None: - del services[key] - - return services -def _save_services(services): +def _save_regenconf_infos(infos): """ - Save managed services to files - + Save the regen conf informations Keyword argument: - services -- A dict of managed services with their parameters - + categories -- A dict containing the regenconf infos """ try: - with open('/etc/yunohost/services.yml', 'w') as f: - yaml.safe_dump(services, f, default_flow_style=False) + with open(REGEN_CONF_FILE, 'w') as f: + yaml.safe_dump(infos, f, default_flow_style=False) except Exception as e: - logger.warning('Error while saving services, exception: %s', e, exc_info=1) + logger.warning('Error while saving regenconf infos, exception: %s', e, exc_info=1) raise @@ -426,7 +416,7 @@ def _get_pending_conf(categories=[]): def _get_conf_hashes(category): """Get the registered conf hashes for a category""" - categories = _get_categories() + categories = _get_regenconf_infos() if category not in categories: logger.debug("category %s is not in categories.yml yet.", category) @@ -444,7 +434,8 @@ def _update_conf_hashes(category, hashes): """Update the registered conf hashes for a category""" logger.debug("updating conf hashes for '%s' with: %s", category, hashes) - categories = _get_categories() + + categories = _get_regenconf_infos() category_conf = categories.get(category, {}) # Handle the case where categories[category] is set to null in the yaml @@ -453,7 +444,7 @@ def _update_conf_hashes(category, hashes): category_conf['conffiles'] = hashes categories[category] = category_conf - _save_categories(categories) + _save_regenconf_infos(categories) def _process_regen_conf(system_conf, new_conf=None, save=True): From 96bd6f8deb5683475bb5daa23ad2489419e909cc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 18:50:46 +0100 Subject: [PATCH 237/721] Fix a few things for service_regen_conf backward compatibility --- src/yunohost/service.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index d088f0029..56cac7a55 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -414,12 +414,20 @@ def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False, list_pending=False): services = _get_services() + + if isinstance(names, str): + names = [names] + for name in names: - if name not in services: - raise YunohostError('service_unknown', service=service) + if name not in services.keys(): + raise YunohostError('service_unknown', service=name) + + if names is []: + names = services.keys() logger.warning(m18n.n("service_regen_conf_is_deprecated")) + from yunohost.regenconf import regen_conf return regen_conf(names, with_diff, force, dry_run, list_pending) From 0ebbb83191273ca716ecaf1c128ece0705b84c82 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 19:03:43 +0100 Subject: [PATCH 238/721] Add migration for services.yml on existing instance --- data/templates/yunohost/services.yml | 9 ++-- .../0009_decouple_regenconf_from_services.py | 42 +++++++++++++++++++ src/yunohost/regenconf.py | 12 ++++++ 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py diff --git a/data/templates/yunohost/services.yml b/data/templates/yunohost/services.yml index 62509e1e9..0d79b182f 100644 --- a/data/templates/yunohost/services.yml +++ b/data/templates/yunohost/services.yml @@ -20,8 +20,6 @@ mysql: glances: {} ssh: log: /var/log/auth.log -ssl: - status: null metronome: log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err] slapd: @@ -34,10 +32,9 @@ yunohost-firewall: need_lock: true nslcd: log: /var/log/syslog -nsswitch: - status: null -yunohost: - status: null +nsswitch: null +ssl: null +yunohost: null bind9: null tahoe-lafs: null memcached: null diff --git a/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py b/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py new file mode 100644 index 000000000..e65aadfdf --- /dev/null +++ b/src/yunohost/data_migrations/0009_decouple_regenconf_from_services.py @@ -0,0 +1,42 @@ +import os + +from moulinette import m18n +from moulinette.utils.log import getActionLogger + +from moulinette.utils.filesystem import read_file +from yunohost.service import _get_services, _save_services +from yunohost.regenconf import _update_conf_hashes + +from yunohost.tools import Migration + +logger = getActionLogger('yunohost.migration') + + +class MyMigration(Migration): + """ + Decouple the regen conf mechanism from the concept of services + """ + + def migrate(self): + + if "conffiles" not in read_file("/etc/yunohost/services.yml") \ + or os.path.exists("/etc/yunohost/regenconf.yml"): + logger.warning(m18n.n("migration_0009_not_needed")) + return + + # For all services + services = _get_services() + for service, infos in services.items(): + # If there are some conffiles (file hashes) + if "conffiles" in infos.keys(): + # Save them using the new regen conf thingy + _update_conf_hashes(service, infos["conffiles"]) + # And delete the old conffile key from the service infos + del services[service]["conffiles"] + + # (Actually save the modification of services) + _save_services(services) + + def backward(self): + + pass diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 783b50c4a..3ea8ccb6d 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -31,6 +31,7 @@ from datetime import datetime from moulinette import m18n from moulinette.utils import log, filesystem +from moulinette.utils.filesystem import read_file from yunohost.utils.error import YunohostError from yunohost.log import is_unit_operation @@ -60,6 +61,17 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run list_pending -- List pending configuration files and exit """ + + # Legacy code to automatically run the migration + # This is required because regen_conf is called before the migration call + # in debian's postinst script + if os.path.exists("/etc/yunohost/installed") \ + and ("conffiles" in read_file("/etc/yunohost/services.yml") \ + or not os.path.exists("/etc/yunohost/regenconf.yml")): + from yunohost.tools import _get_migration_by_name + migration = _get_migration_by_name("decouple_regenconf_from_services") + migration.migrate() + result = {} # Return the list of pending conf From d7d224286237f7bb8733bdccb19ad348674f1331 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 19:10:01 +0100 Subject: [PATCH 239/721] Fix imports in some migrations --- src/yunohost/data_migrations/0003_migrate_to_stretch.py | 6 +++--- .../0007_ssh_conf_managed_by_yunohost_step1.py | 7 ++----- .../0008_ssh_conf_managed_by_yunohost_step2.py | 3 +-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/yunohost/data_migrations/0003_migrate_to_stretch.py b/src/yunohost/data_migrations/0003_migrate_to_stretch.py index 438393216..0db719e15 100644 --- a/src/yunohost/data_migrations/0003_migrate_to_stretch.py +++ b/src/yunohost/data_migrations/0003_migrate_to_stretch.py @@ -10,9 +10,9 @@ from moulinette.utils.filesystem import read_file from yunohost.tools import Migration from yunohost.app import unstable_apps -from yunohost.service import (_run_service_command, - manually_modified_files, - manually_modified_files_compared_to_debian_default) +from yunohost.service import _run_service_command +from yunohost.regenconf import (manually_modified_files, + manually_modified_files_compared_to_debian_default) from yunohost.utils.filesystem import free_space_in_directory from yunohost.utils.packages import get_installed_version from yunohost.utils.network import get_network_interfaces diff --git a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py index 39a7d34e3..959b17fb5 100644 --- a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py +++ b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py @@ -3,15 +3,12 @@ import re from shutil import copyfile -from moulinette import m18n from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import mkdir, rm from yunohost.tools import Migration -from yunohost.service import _get_conf_hashes, \ - _calculate_hash, \ - _run_service_command -from yunohost.regen_conf import regen_conf +from yunohost.service import _run_service_command +from yunohost.regenconf import regen_conf from yunohost.settings import settings_set from yunohost.utils.error import YunohostError diff --git a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py index fce44298d..8984440bd 100644 --- a/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py +++ b/src/yunohost/data_migrations/0008_ssh_conf_managed_by_yunohost_step2.py @@ -6,8 +6,7 @@ from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import chown from yunohost.tools import Migration -from yunohost.service import _get_conf_hashes, \ - _calculate_hash +from yunohost.regenconf import _get_conf_hashes, _calculate_hash from yunohost.regenconf import regen_conf from yunohost.settings import settings_set, settings_get from yunohost.utils.error import YunohostError From 984646bb3cd6866ce36e146620ad62fe3361e048 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 19:22:19 +0100 Subject: [PATCH 240/721] Implement strings for migration --- locales/en.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/en.json b/locales/en.json index 5ab2a749b..a606a13dc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -286,6 +286,7 @@ "migration_description_0006_sync_admin_and_root_passwords": "Synchronize admin and root passwords", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", + "migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services", "migration_0003_backward_impossible": "The stretch migration cannot be reverted.", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", @@ -311,6 +312,7 @@ "migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server;", "migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", "migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.", + "migration_0009_not_needed": "This migration already happened somehow ? Skipping.", "migrations_backward": "Migrating backward.", "migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}", "migrations_cant_reach_migration_file": "Can't access migrations files at path %s", From 69b8f7294b91360f3e937a2b3ab5cf7bd9515331 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Feb 2019 19:36:06 +0100 Subject: [PATCH 241/721] Fix log handling --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index a606a13dc..c10e8c393 100644 --- a/locales/en.json +++ b/locales/en.json @@ -249,7 +249,7 @@ "log_selfsigned_cert_install": "Install self signed certificate on '{}' domain", "log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate", "log_service_enable": "Enable '{}' service", - "log_service_regen_conf": "Regenerate system configurations '{}'", + "log_regen_conf": "Regenerate system configurations '{}'", "log_user_create": "Add '{}' user", "log_user_delete": "Delete '{}' user", "log_user_update": "Update information of '{}' user", From 1b7d25de96d215101547d08babfa2d884343518d Mon Sep 17 00:00:00 2001 From: Kayou Date: Tue, 19 Feb 2019 00:03:46 +0100 Subject: [PATCH 242/721] Update psql --- data/helpers.d/psql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 2ef13482a..2feb6b0ac 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -1,3 +1,5 @@ +#!/bin/bash + # Create a master password and set up global settings # Please always call this script in install and restore scripts # From b5ae91b34e8edd4cefe99ef56be891b72b663e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Bourr=C3=A9?= Date: Tue, 19 Feb 2019 01:39:00 +0100 Subject: [PATCH 243/721] [WIP] Update --- data/helpers.d/psql | 216 +++++++++++++++++++++++++++++++++----------- 1 file changed, 162 insertions(+), 54 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 2feb6b0ac..20edb2fc5 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -1,75 +1,147 @@ #!/bin/bash -# Create a master password and set up global settings -# Please always call this script in install and restore scripts -# -# usage: ynh_psql_test_if_first_run -ynh_psql_test_if_first_run() { - if [ -f /etc/yunohost/psql ]; - then - echo "PostgreSQL is already installed, no need to create master password" - else - local pgsql="$(ynh_string_random)" - echo "$pgsql" > /etc/yunohost/psql - - if [ -e /etc/postgresql/9.4/ ] - then - local pg_hba=/etc/postgresql/9.4/main/pg_hba.conf - elif [ -e /etc/postgresql/9.6/ ] - then - local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf - else - ynh_die "postgresql shoud be 9.4 or 9.6" - fi - - systemctl start postgresql - sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$pgsql'" postgres - - # force all user to connect to local database using passwords - # https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html#EXAMPLE-PG-HBA.CONF - # Note: we can't use peer since YunoHost create users with nologin - # See: https://github.com/YunoHost/yunohost/blob/unstable/data/helpers.d/user - sed -i '/local\s*all\s*all\s*peer/i \ - local all all password' "$pg_hba" - systemctl enable postgresql - systemctl reload postgresql - fi -} +PSQL_ROOT_PWD_FILE=/etc/yunohost/psql # Open a connection as a user # # example: ynh_psql_connect_as 'user' 'pass' <<< "UPDATE ...;" # example: ynh_psql_connect_as 'user' 'pass' < /path/to/file.sql # -# usage: ynh_psql_connect_as user pwd [db] -# | arg: user - the user name to connect as -# | arg: pwd - the user password -# | arg: db - the database to connect to +# usage: ynh_psql_connect_as --user=user --password=password [--database=database] +# | arg: -u, --user - the user name to connect as +# | arg: -p, --password - the user password +# | arg: -d, --database - the database to connect to ynh_psql_connect_as() { - local user="$1" - local pwd="$2" - local db="$3" - sudo --login --user=postgres PGUSER="$user" PGPASSWORD="$pwd" psql "$db" + # Declare an array to define the options of this helper. + local legacy_args=upd + declare -Ar args_array=( [u]=user= [p]=password= [d]=database= ) + local user + local password + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" + + sudo --login --user=postgres PGUSER="$user" PGPASSWORD="$password" psql "$database" } -# # Execute a command as root user +# Execute a command as root user # -# usage: ynh_psql_execute_as_root sql [db] -# | arg: sql - the SQL command to execute +# usage: ynh_psql_execute_as_root --sql=sql [--database=database] +# | arg: -s, --sql - the SQL command to execute +# | arg: -d, --database - the database to connect to ynh_psql_execute_as_root () { - local sql="$1" - sudo --login --user=postgres psql <<< "$sql" + # Declare an array to define the options of this helper. + local legacy_args=sd + declare -Ar args_array=( [s]=sql= [d]=database= ) + local sql + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" + + ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ + --database="$database" <<< "$sql" } # Execute a command from a file as root user # -# usage: ynh_psql_execute_file_as_root file [db] -# | arg: file - the file containing SQL commands -# | arg: db - the database to connect to +# usage: ynh_psql_execute_file_as_root --file=file [--database=database] +# | arg: -f, --file - the file containing SQL commands +# | arg: -d, --database - the database to connect to ynh_psql_execute_file_as_root() { - local file="$1" - local db="$2" - sudo --login --user=postgres psql "$db" < "$file" + # Declare an array to define the options of this helper. + local legacy_args=fd + declare -Ar args_array=( [f]=file= [d]=database= ) + local file + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" + + ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ + --database="$database" < "$file" +} + +# Create a database and grant optionnaly privilegies to a user +# +# [internal] +# +# usage: ynh_psql_create_db db [user [pwd]] +# | arg: db - the database name to create +# | arg: user - the user to grant privilegies +# | arg: pwd - the password to identify user by +ynh_psql_create_db() { + local db=$1 + + ynh_psql_create_user "$user" "$pwd" + sudo --login --user=postgres createdb --owner="$user" "$db" +} + +# Drop a database +# +# [internal] +# +# If you intend to drop the database *and* the associated user, +# consider using ynh_psql_remove_db instead. +# +# usage: ynh_psql_drop_db db +# | arg: db - the database name to drop +ynh_psql_drop_db() { + local db=$1 + sudo --login --user=postgres dropdb $db +} + +# Dump a database +# +# example: ynh_psql_dump_db 'roundcube' > ./dump.sql +# +# usage: ynh_psql_dump_db --database=database +# | arg: -d, --database - the database name to dump +# | ret: the psqldump output +ynh_psql_dump_db() { + # Declare an array to define the options of this helper. + local legacy_args=d + declare -Ar args_array=( [d]=database= ) + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + sudo --login --user=postgres pg_dump "$database" +} + +# Create a user +# +# [internal] +# +# usage: ynh_psql_create_user user pwd [host] +# | arg: user - the user name to create +# | arg: pwd - the password to identify user by +ynh_psql_create_user() { + local user=$1 + local psql=$2 + ynh_psql_execute_as_root "CREATE USER $user WITH PASSWORD '$pwd'" +} + +# Check if a psql user exists +# +# usage: ynh_psql_user_exists --user=user +# | arg: -u, --user - the user for which to check existence +ynh_psql_user_exists() +{ + # Declare an array to define the options of this helper. + local legacy_args=u + declare -Ar args_array=( [u]=user= ) + local user + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + if [[ -z $(ynh_psql_execute_as_root --sql="SELECT 1 FROM pg_roles WHERE rolename='$user';") ]] + then + return 1 + else + return 0 + fi } # Create a database, an user and its password. Then store the password in the app's config @@ -148,3 +220,39 @@ ynh_psql_drop_user() { local user="$1" sudo --login --user=postgres dropuser "$user" } + +# Create a master password and set up global settings +# Please always call this script in install and restore scripts +# +# usage: ynh_psql_test_if_first_run +ynh_psql_test_if_first_run() { + if [ -f /etc/yunohost/psql ]; + then + echo "PostgreSQL is already installed, no need to create master password" + else + local pgsql="$(ynh_string_random)" + echo "$pgsql" > /etc/yunohost/psql + + if [ -e /etc/postgresql/9.4/ ] + then + local pg_hba=/etc/postgresql/9.4/main/pg_hba.conf + elif [ -e /etc/postgresql/9.6/ ] + then + local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf + else + ynh_die "postgresql shoud be 9.4 or 9.6" + fi + + systemctl start postgresql + sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$pgsql'" postgres + + # force all user to connect to local database using passwords + # https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html#EXAMPLE-PG-HBA.CONF + # Note: we can't use peer since YunoHost create users with nologin + # See: https://github.com/YunoHost/yunohost/blob/unstable/data/helpers.d/user + sed -i '/local\s*all\s*all\s*peer/i \ + local all all password' "$pg_hba" + systemctl enable postgresql + systemctl reload postgresql + fi +} \ No newline at end of file From 9a7dc6d43e63fe776e5d4053786595a97c4aabda Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Feb 2019 16:40:47 +0100 Subject: [PATCH 244/721] Be able to define hook to trigger after changing a setting value --- src/yunohost/settings.py | 51 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index bbfb3ca56..cb98fa57e 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -115,10 +115,18 @@ def settings_set(key, value): raise YunohostError('global_settings_unknown_type', setting=key, unknown_type=key_type) + old_value = settings[key].get("value") settings[key]["value"] = value - _save_settings(settings) + # TODO : whatdo if the old value is the same as + # the new value... + try: + trigger_post_change_hook(key, old_value, value) + except Exception as e: + logger.error("Post-change hook for setting %s failed : %s" % (key, e)) + raise + def settings_reset(key): """ @@ -235,3 +243,44 @@ def _save_settings(settings, location=SETTINGS_PATH): settings_fd.write(result) except Exception as e: raise YunohostError('global_settings_cant_write_settings', reason=e) + + +# Meant to be a dict of setting_name -> function to call +post_change_hooks = {} + + +def post_change_hook(setting_name): + def decorator(func): + assert setting_name in DEFAULTS.keys(), "The setting %s does not exists" % setting_name + assert setting_name not in post_change_hooks, "You can only register one post change hook per setting (in particular for %s)" % setting_name + post_change_hooks[setting_name] = func + return func + return decorator + + +def trigger_post_change_hook(setting_name, old_value, new_value): + if setting_name not in post_change_hooks: + logger.debug("Nothing to do after changing setting %s" % setting_name) + return + + f = post_change_hooks[setting_name] + f(old_value, new_value) + + +# =========================================== +# +# Actions to trigger when changing a setting +# You can define such an action with : +# +# @post_change_hook("your.setting.name") +# def some_function_name(old_value, new_value): +# # Do some stuff +# +# =========================================== + + +#@post_change_hook("example.int") +#def myfunc(old_value, new_value): +# print("In hook") +# print(old_value) +# print(new_value) From 39ae73604400ecb050af5c8df3d26d8180f236f9 Mon Sep 17 00:00:00 2001 From: Kayou Date: Tue, 19 Feb 2019 17:43:33 +0100 Subject: [PATCH 245/721] Ensure the tar file is closed --- src/yunohost/backup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index ed7799fc1..9268c7613 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1802,10 +1802,11 @@ class TarBackupMethod(BackupMethod): # Add the "source" into the archive and transform the path into # "dest" tar.add(path['source'], arcname=path['dest']) - tar.close() except IOError: logger.error(m18n.n('backup_archive_writing_error'), exc_info=1) raise YunohostError('backup_creation_failed') + finally: + tar.close() # Move info file shutil.copy(os.path.join(self.work_dir, 'info.json'), From a69cd443aece34d7225cb281acc2019583980985 Mon Sep 17 00:00:00 2001 From: Kayou Date: Tue, 19 Feb 2019 17:49:57 +0100 Subject: [PATCH 246/721] More explicit error --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 8528c2576..244eba80f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -75,7 +75,7 @@ "backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'", "backup_archive_open_failed": "Unable to open the backup archive", "backup_archive_system_part_not_available": "System part '{part:s}' not available in this backup", - "backup_archive_writing_error": "Unable to add files to backup into the compressed archive", + "backup_archive_writing_error": "Unable to add files '{source:s}' (named in the archive: '{dest:s}') to backup into the compressed archive '{archive:s}'", "backup_ask_for_copying_if_needed": "Some files couldn't be prepared to be backuped using the method that avoid to temporarily waste space on the system. To perform the backup, {size:s}MB should be used temporarily. Do you agree?", "backup_borg_not_implemented": "Borg backup method is not yet implemented", "backup_cant_mount_uncompress_archive": "Unable to mount in readonly mode the uncompress archive directory", From 44455cd433eaf529e40df921fef1c33de3505086 Mon Sep 17 00:00:00 2001 From: Kayou Date: Tue, 19 Feb 2019 17:50:12 +0100 Subject: [PATCH 247/721] More explicit error --- src/yunohost/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 9268c7613..062343a46 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1803,7 +1803,7 @@ class TarBackupMethod(BackupMethod): # "dest" tar.add(path['source'], arcname=path['dest']) except IOError: - logger.error(m18n.n('backup_archive_writing_error'), exc_info=1) + logger.error(m18n.n('backup_archive_writing_error', source=path['source'], archive=self._archive_file, dest=path['dest']), exc_info=1) raise YunohostError('backup_creation_failed') finally: tar.close() From ddf2b49d544f2b77aaaf0562396e6ca5075d4c67 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Feb 2019 17:57:38 +0100 Subject: [PATCH 248/721] Cache results from meltdown checker --- src/yunohost/tools.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index b2fbf380c..88f202cc7 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -713,6 +713,23 @@ def tools_diagnosis(auth, private=False): def _check_if_vulnerable_to_meltdown(): # meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754 + # We use a cache file to avoid re-running the script so many times, + # which can be expensive (up to around 5 seconds on ARM) + # and make the admin appear to be slow (c.f. the calls to diagnosis + # from the webadmin) + # + # The cache is in /tmp and shall disappear upon reboot + # *or* we compare it to dpkg.log modification time + # such that it's re-ran if there was package upgrades + # (e.g. from yunohost) + cache_file = "/tmp/yunohost-meltdown-diagnosis" + dpkg_log = "/var/log/dpkg.log" + print(os.path.exists(cache_file)) + if os.path.exists(cache_file): + if not os.path.exists(dpkg_log) or os.path.getmtime(cache_file) > os.path.getmtime(dpkg_log): + logger.debug("Using cached results for meltdown checker, from %s" % cache_file) + return read_json(cache_file)[0]["VULNERABLE"] + # script taken from https://github.com/speed47/spectre-meltdown-checker # script commit id is store directly in the script file_dir = os.path.split(__file__)[0] @@ -722,6 +739,7 @@ def _check_if_vulnerable_to_meltdown(): # example output from the script: # [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}] try: + logger.debug("Running meltdown vulnerability checker") call = subprocess.Popen("bash %s --batch json --variant 3" % SCRIPT_PATH, shell=True, stdout=subprocess.PIPE, @@ -752,6 +770,8 @@ def _check_if_vulnerable_to_meltdown(): logger.warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e) raise Exception("Command output for failed meltdown check: '%s'" % output) + logger.debug("Writing results from meltdown checker to cache file, %s" % cache_file) + write_to_json(cache_file, CVEs) return CVEs[0]["VULNERABLE"] From b2a606987cc8224b7d129f53fe470f81235d68cb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Feb 2019 18:05:09 +0100 Subject: [PATCH 249/721] It was a debug print indeed :D --- src/yunohost/tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 88f202cc7..4a2101121 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -724,7 +724,6 @@ def _check_if_vulnerable_to_meltdown(): # (e.g. from yunohost) cache_file = "/tmp/yunohost-meltdown-diagnosis" dpkg_log = "/var/log/dpkg.log" - print(os.path.exists(cache_file)) if os.path.exists(cache_file): if not os.path.exists(dpkg_log) or os.path.getmtime(cache_file) > os.path.getmtime(dpkg_log): logger.debug("Using cached results for meltdown checker, from %s" % cache_file) From 3c33eb078d3f87c97b59ef7bdb1e51ddd3f2702e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 Feb 2019 18:43:14 +0100 Subject: [PATCH 250/721] [microdecision] Fix interface with meltdown checker script, stdout contains weird debug messages when ran inside LXC :| --- src/yunohost/tools.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 915b63940..4d4f5fffe 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -735,6 +735,14 @@ def _check_if_vulnerable_to_meltdown(): output, err = call.communicate() assert call.returncode in (0, 2, 3), "Return code: %s" % call.returncode + # If there are multiple lines, sounds like there was some messages + # in stdout that are not json >.> ... Try to get the actual json + # stuff which should be the last line + output = output.strip() + if "\n" in output: + logger.debug("Original meltdown checker output : %s" % output) + output = output.split("\n")[-1] + CVEs = json.loads(output) assert len(CVEs) == 1 assert CVEs[0]["NAME"] == "MELTDOWN" From ce79dd8c8a3ca1f16dda46cfd749714e3eb1fc07 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Feb 2019 19:12:59 +0100 Subject: [PATCH 251/721] Update changelog for 3.4.2.4 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index 3f3a6a5ef..7be4212fe 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.4.2.4) stable; urgency=low + + - [fix] Meltdown vulnerability checker something outputing trash instead of pure json + + -- Alexandre Aubin Tue, 19 Feb 2019 19:11:38 +0000 + yunohost (3.4.2.3) stable; urgency=low - [fix] Admin password appearing in logs after logging in on webadmin From d5ca4dd88b3fe49d3c8b9a0780b97e61c1c0e89e Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 20 Feb 2019 00:21:44 +0100 Subject: [PATCH 252/721] Fix ynh_psql_user_exists --- data/helpers.d/psql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 20edb2fc5..b6ba5afaf 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -120,7 +120,7 @@ ynh_psql_dump_db() { ynh_psql_create_user() { local user=$1 local psql=$2 - ynh_psql_execute_as_root "CREATE USER $user WITH PASSWORD '$pwd'" + ynh_psql_execute_as_root --sql="CREATE USER $user WITH PASSWORD '$pwd'" } # Check if a psql user exists @@ -136,7 +136,7 @@ ynh_psql_user_exists() # Manage arguments with getopts ynh_handle_getopts_args "$@" - if [[ -z $(ynh_psql_execute_as_root --sql="SELECT 1 FROM pg_roles WHERE rolename='$user';") ]] + if [[ -n $(sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';") ]] then return 1 else From d030628a9b36d869e39921f502b9e7f5b58bb1ad Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 20 Feb 2019 01:27:27 +0100 Subject: [PATCH 253/721] Update psql helper --- data/helpers.d/psql | 264 +++++++++++++++++++++++--------------------- 1 file changed, 137 insertions(+), 127 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index b6ba5afaf..fb9ffe013 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -12,15 +12,15 @@ PSQL_ROOT_PWD_FILE=/etc/yunohost/psql # | arg: -p, --password - the user password # | arg: -d, --database - the database to connect to ynh_psql_connect_as() { - # Declare an array to define the options of this helper. - local legacy_args=upd - declare -Ar args_array=( [u]=user= [p]=password= [d]=database= ) - local user - local password - local database - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - database="${database:-}" + # Declare an array to define the options of this helper. + local legacy_args=upd + declare -Ar args_array=([u]=user= [p]=password= [d]=database=) + local user + local password + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" sudo --login --user=postgres PGUSER="$user" PGPASSWORD="$password" psql "$database" } @@ -30,18 +30,18 @@ ynh_psql_connect_as() { # usage: ynh_psql_execute_as_root --sql=sql [--database=database] # | arg: -s, --sql - the SQL command to execute # | arg: -d, --database - the database to connect to -ynh_psql_execute_as_root () { - # Declare an array to define the options of this helper. - local legacy_args=sd - declare -Ar args_array=( [s]=sql= [d]=database= ) - local sql - local database - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - database="${database:-}" +ynh_psql_execute_as_root() { + # Declare an array to define the options of this helper. + local legacy_args=sd + declare -Ar args_array=([s]=sql= [d]=database=) + local sql + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" - ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ - --database="$database" <<< "$sql" + ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ + --database="$database" <<<"$sql" } # Execute a command from a file as root user @@ -50,17 +50,17 @@ ynh_psql_execute_as_root () { # | arg: -f, --file - the file containing SQL commands # | arg: -d, --database - the database to connect to ynh_psql_execute_file_as_root() { - # Declare an array to define the options of this helper. - local legacy_args=fd - declare -Ar args_array=( [f]=file= [d]=database= ) - local file - local database - # Manage arguments with getopts - ynh_handle_getopts_args "$@" - database="${database:-}" + # Declare an array to define the options of this helper. + local legacy_args=fd + declare -Ar args_array=([f]=file= [d]=database=) + local file + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + database="${database:-}" - ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ - --database="$database" < "$file" + ynh_psql_connect_as --user="postgres" --password="$(sudo cat $PSQL_ROOT_PWD_FILE)" \ + --database="$database" <"$file" } # Create a database and grant optionnaly privilegies to a user @@ -72,10 +72,18 @@ ynh_psql_execute_file_as_root() { # | arg: user - the user to grant privilegies # | arg: pwd - the password to identify user by ynh_psql_create_db() { - local db=$1 + local db=$1 - ynh_psql_create_user "$user" "$pwd" - sudo --login --user=postgres createdb --owner="$user" "$db" + local sql="CREATE DATABASE ${db};" + + # grant all privilegies to user + if [[ $# -gt 1 ]]; then + #ynh_psql_create_user "$user" "$pwd" + sql+=" GRANT ALL PRIVILEGES ON ${db} TO ${2} WITH GRANT OPTION;" + fi + + #sudo --login --user=postgres createdb --owner="$user" "$db" + ynh_psql_execute_as_root --sql="$sql" } # Drop a database @@ -89,7 +97,7 @@ ynh_psql_create_db() { # | arg: db - the database name to drop ynh_psql_drop_db() { local db=$1 - sudo --login --user=postgres dropdb $db + sudo --login --user=postgres dropdb $db } # Dump a database @@ -100,14 +108,14 @@ ynh_psql_drop_db() { # | arg: -d, --database - the database name to dump # | ret: the psqldump output ynh_psql_dump_db() { - # Declare an array to define the options of this helper. - local legacy_args=d - declare -Ar args_array=( [d]=database= ) - local database - # Manage arguments with getopts - ynh_handle_getopts_args "$@" + # Declare an array to define the options of this helper. + local legacy_args=d + declare -Ar args_array=([d]=database=) + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - sudo --login --user=postgres pg_dump "$database" + sudo --login --user=postgres pg_dump "$database" } # Create a user @@ -127,21 +135,48 @@ ynh_psql_create_user() { # # usage: ynh_psql_user_exists --user=user # | arg: -u, --user - the user for which to check existence -ynh_psql_user_exists() -{ - # Declare an array to define the options of this helper. - local legacy_args=u - declare -Ar args_array=( [u]=user= ) - local user - # Manage arguments with getopts - ynh_handle_getopts_args "$@" +ynh_psql_user_exists() { + # Declare an array to define the options of this helper. + local legacy_args=u + declare -Ar args_array=([u]=user=) + local user + # Manage arguments with getopts + ynh_handle_getopts_args "$@" - if [[ -n $(sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';") ]] - then - return 1 - else - return 0 - fi + if [[ -n $(sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';") ]]; then + return 1 + else + return 0 + fi +} + +# Check if a psql database exists +# +# usage: ynh_psql_database_exists --database=database +# | arg: -d, --database - the database for which to check existence +ynh_psql_database_exists() { + # Declare an array to define the options of this helper. + local legacy_args=u + declare -Ar args_array=([u]=database=) + local database + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + if [[ -n $(sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';") ]]; then + return 1 + else + return 0 + fi +} + +# Drop a user +# +# [internal] +# +# usage: ynh_psql_drop_user user +# | arg: user - the user name to drop +ynh_psql_drop_user() { + ynh_psql_execute_as_root --sql="DROP USER '${1}';" } # Create a database, an user and its password. Then store the password in the app's config @@ -149,76 +184,54 @@ ynh_psql_user_exists() # After executing this helper, the password of the created database will be available in $db_pwd # It will also be stored as "psqlpwd" into the app settings. # -# usage: ynh_psql_setup_db user name [pwd] -# | arg: user - Owner of the database -# | arg: name - Name of the database -# | arg: pwd - Password of the database. If not given, a password will be generated -ynh_psql_setup_db () { - local db_user="$1" - local db_name="$2" - local new_db_pwd=$(ynh_string_random) # Generate a random password - # If $3 is not given, use new_db_pwd instead for db_pwd. - local db_pwd="${3:-$new_db_pwd}" - ynh_psql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database - ynh_app_setting_set "$app" psqlpwd "$db_pwd" # Store the password in the app's config +# usage: ynh_psql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] +# | arg: -u, --db_user - Owner of the database +# | arg: -n, --db_name - Name of the database +# | arg: -p, --db_pwd - Password of the database. If not given, a password will be generated +ynh_psql_setup_db() { + # Declare an array to define the options of this helper. + local legacy_args=unp + declare -Ar args_array=([u]=db_user= [n]=db_name= [p]=db_pwd=) + local db_user + local db_name + db_pwd="" + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + local new_db_pwd=$(ynh_string_random) # Generate a random password + # If $db_pwd is not given, use new_db_pwd instead for db_pwd + db_pwd="${db_pwd:-$new_db_pwd}" + + ynh_psql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database + ynh_app_setting_set --app=$app --key=psqlpwd --value=$db_pwd # Store the password in the app's config } -# Create a database and grant privilegies to a user +# Remove a database if it exists, and the associated user # -# usage: ynh_psql_create_db db [user [pwd]] -# | arg: db - the database name to create -# | arg: user - the user to grant privilegies -# | arg: pwd - the user password -ynh_psql_create_db() { - local db="$1" - local user="$2" - local pwd="$3" - ynh_psql_create_user "$user" "$pwd" - sudo --login --user=postgres createdb --owner="$user" "$db" -} - -# Drop a database -# -# usage: ynh_psql_drop_db db -# | arg: db - the database name to drop -# | arg: user - the user to drop +# usage: ynh_psql_remove_db --db_user=user --db_name=name +# | arg: -u, --db_user - Owner of the database +# | arg: -n, --db_name - Name of the database ynh_psql_remove_db() { - local db="$1" - local user="$2" - sudo --login --user=postgres dropdb "$db" - ynh_psql_drop_user "$user" -} + # Declare an array to define the options of this helper. + local legacy_args=un + declare -Ar args_array=([u]=db_user= [n]=db_name=) + local db_user + local db_name + # Manage arguments with getopts + ynh_handle_getopts_args "$@" -# Dump a database -# -# example: ynh_psql_dump_db 'roundcube' > ./dump.sql -# -# usage: ynh_psql_dump_db db -# | arg: db - the database name to dump -# | ret: the psqldump output -ynh_psql_dump_db() { - local db="$1" - sudo --login --user=postgres pg_dump "$db" -} + local psql_root_password=$(sudo cat $PSQL_ROOT_PWD_FILE) + if ynh_psql_database_exists "$db_name"; then # Check if the database exists + echo "Removing database $db_name" >&2 + ynh_psql_drop_db $db_name # Remove the database + else + echo "Database $db_name not found" >&2 + fi - -# Create a user -# -# usage: ynh_psql_create_user user pwd [host] -# | arg: user - the user name to create -ynh_psql_create_user() { - local user="$1" - local pwd="$2" - sudo --login --user=postgres psql -c"CREATE USER $user WITH PASSWORD '$pwd'" postgres -} - -# Drop a user -# -# usage: ynh_psql_drop_user user -# | arg: user - the user name to drop -ynh_psql_drop_user() { - local user="$1" - sudo --login --user=postgres dropuser "$user" + # Remove psql user if it exists + if $(ynh_psql_user_exists --user=$db_user); then + ynh_psql_drop_user $db_user + fi } # Create a master password and set up global settings @@ -226,18 +239,15 @@ ynh_psql_drop_user() { # # usage: ynh_psql_test_if_first_run ynh_psql_test_if_first_run() { - if [ -f /etc/yunohost/psql ]; - then + if [ -f /etc/yunohost/psql ]; then echo "PostgreSQL is already installed, no need to create master password" else local pgsql="$(ynh_string_random)" - echo "$pgsql" > /etc/yunohost/psql + echo "$pgsql" >/etc/yunohost/psql - if [ -e /etc/postgresql/9.4/ ] - then + if [ -e /etc/postgresql/9.4/ ]; then local pg_hba=/etc/postgresql/9.4/main/pg_hba.conf - elif [ -e /etc/postgresql/9.6/ ] - then + elif [ -e /etc/postgresql/9.6/ ]; then local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf else ynh_die "postgresql shoud be 9.4 or 9.6" @@ -255,4 +265,4 @@ ynh_psql_test_if_first_run() { systemctl enable postgresql systemctl reload postgresql fi -} \ No newline at end of file +} From f2a4be29920261f0f95921be1cbf84ac2c516374 Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 20 Feb 2019 01:39:22 +0100 Subject: [PATCH 254/721] fix ynh_psql_create_db --- data/helpers.d/psql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index fb9ffe013..7843212eb 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -79,7 +79,7 @@ ynh_psql_create_db() { # grant all privilegies to user if [[ $# -gt 1 ]]; then #ynh_psql_create_user "$user" "$pwd" - sql+=" GRANT ALL PRIVILEGES ON ${db} TO ${2} WITH GRANT OPTION;" + sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${2} WITH GRANT OPTION;" fi #sudo --login --user=postgres createdb --owner="$user" "$db" From a7af86832eaa134f6613ebff47244841dec8361a Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 20 Feb 2019 01:56:14 +0100 Subject: [PATCH 255/721] Small fix --- data/helpers.d/psql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 7843212eb..d4888982c 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -221,7 +221,7 @@ ynh_psql_remove_db() { ynh_handle_getopts_args "$@" local psql_root_password=$(sudo cat $PSQL_ROOT_PWD_FILE) - if ynh_psql_database_exists "$db_name"; then # Check if the database exists + if $(ynh_psql_database_exists "$db_name"); then # Check if the database exists echo "Removing database $db_name" >&2 ynh_psql_drop_db $db_name # Remove the database else From 203b8c06a999766ecf2229588787c203ca37c582 Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 20 Feb 2019 02:18:24 +0100 Subject: [PATCH 256/721] Fix ynh_psql_user_exists and ynh_psql_database_exists --- data/helpers.d/psql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index d4888982c..a3069ce11 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -143,7 +143,7 @@ ynh_psql_user_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if [[ -n $(sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';") ]]; then + if [[ -z $(sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';") ]]; then return 1 else return 0 @@ -162,7 +162,7 @@ ynh_psql_database_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if [[ -n $(sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';") ]]; then + if [[ -z $(sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';") ]]; then return 1 else return 0 From a20b0e96c809a4b7d23597eea82bbc7f6ce0baa5 Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 20 Feb 2019 02:18:38 +0100 Subject: [PATCH 257/721] Fix ynh_psql_setup_db --- data/helpers.d/psql | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index a3069ce11..a48aef0fb 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -78,11 +78,9 @@ ynh_psql_create_db() { # grant all privilegies to user if [[ $# -gt 1 ]]; then - #ynh_psql_create_user "$user" "$pwd" sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${2} WITH GRANT OPTION;" fi - #sudo --login --user=postgres createdb --owner="$user" "$db" ynh_psql_execute_as_root --sql="$sql" } @@ -128,7 +126,7 @@ ynh_psql_dump_db() { ynh_psql_create_user() { local user=$1 local psql=$2 - ynh_psql_execute_as_root --sql="CREATE USER $user WITH PASSWORD '$pwd'" + ynh_psql_execute_as_root --sql="CREATE USER $user WITH PASSWORD $pwd" } # Check if a psql user exists @@ -176,7 +174,7 @@ ynh_psql_database_exists() { # usage: ynh_psql_drop_user user # | arg: user - the user name to drop ynh_psql_drop_user() { - ynh_psql_execute_as_root --sql="DROP USER '${1}';" + ynh_psql_execute_as_root --sql="DROP USER ${1};" } # Create a database, an user and its password. Then store the password in the app's config @@ -202,6 +200,10 @@ ynh_psql_setup_db() { # If $db_pwd is not given, use new_db_pwd instead for db_pwd db_pwd="${db_pwd:-$new_db_pwd}" + if [ $(ynh_psql_user_exists --user=$db_user) ]; then + ynh_psql_create_user "$db_name" "$db_user" "$db_pwd" + fi + ynh_psql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database ynh_app_setting_set --app=$app --key=psqlpwd --value=$db_pwd # Store the password in the app's config } @@ -230,7 +232,10 @@ ynh_psql_remove_db() { # Remove psql user if it exists if $(ynh_psql_user_exists --user=$db_user); then + echo "Removing user $db_user" >&2 ynh_psql_drop_user $db_user + else + echo "User $db_user not found" >&2 fi } From c7245e5db1babff3746411a595a3f480fe9e9940 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 20 Feb 2019 15:02:14 +0100 Subject: [PATCH 258/721] Add apt-transport-https to dependencies --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index b359d5ec4..685c194ba 100644 --- a/debian/control +++ b/debian/control @@ -13,7 +13,7 @@ Depends: ${python:Depends}, ${misc:Depends} , moulinette (>= 2.7.1), ssowat (>= 2.7.1) , python-psutil, python-requests, python-dnspython, python-openssl , python-apt, python-miniupnpc, python-dbus, python-jinja2 - , glances + , glances, apt-transport-https , dnsutils, bind9utils, unzip, git, curl, cron, wget, jq , ca-certificates, netcat-openbsd, iproute , mariadb-server, php-mysql | php-mysqlnd From 76ad77f829895cc4853db8a83079afdc934547ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 20 Feb 2019 23:02:18 +0100 Subject: [PATCH 259/721] Define permission in app_clearaccess --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 94f6680ce..badebf83f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1034,7 +1034,7 @@ def app_clearaccess(operation_logger, auth, apps): """ from yunohost.permission import user_permission_clear - user_permission_clear(operation_logger, auth, app=apps, permission="main") + permission = user_permission_clear(operation_logger, auth, app=apps, permission="main") result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()} From 96b65f938b3dd1a326a1b2b7d151b5fc242c2ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 20 Feb 2019 23:07:17 +0100 Subject: [PATCH 260/721] Fix dirty fix ssowatconf --- src/yunohost/backup.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index c22182a45..841b81db7 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -919,11 +919,6 @@ class RestoreManager(): successfull_apps = self.targets.list("apps", include=["Success", "Warning"]) - if successfull_apps != []: - # Quickfix: the old app_ssowatconf(auth) instruction failed due to - # ldap restore hooks - os.system('sudo yunohost app ssowatconf') - permission_sync_to_user(auth, force=True) if os.path.ismount(self.work_dir): From f0d8f88121361d2677bb5c2a403c8a2f094d0ed4 Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 20 Feb 2019 23:57:12 +0100 Subject: [PATCH 261/721] Change $psql to $pwd --- data/helpers.d/psql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index a48aef0fb..0c7d70caf 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -125,8 +125,8 @@ ynh_psql_dump_db() { # | arg: pwd - the password to identify user by ynh_psql_create_user() { local user=$1 - local psql=$2 - ynh_psql_execute_as_root --sql="CREATE USER $user WITH PASSWORD $pwd" + local pwd=$2 + ynh_psql_execute_as_root --sql="CREATE USER $user WITH ENCRYPTED PASSWORD '$pwd'" } # Check if a psql user exists From 3ae5955590e0e9ade4cf3c3625e1cb54e574b66c Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 20 Feb 2019 23:58:26 +0100 Subject: [PATCH 262/721] Remove some $() and [] --- data/helpers.d/psql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 0c7d70caf..f960e6297 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -200,8 +200,8 @@ ynh_psql_setup_db() { # If $db_pwd is not given, use new_db_pwd instead for db_pwd db_pwd="${db_pwd:-$new_db_pwd}" - if [ $(ynh_psql_user_exists --user=$db_user) ]; then - ynh_psql_create_user "$db_name" "$db_user" "$db_pwd" + if ! ynh_psql_user_exists --user=$db_user; then + ynh_psql_create_user "$db_user" "$db_pwd" fi ynh_psql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database @@ -223,7 +223,7 @@ ynh_psql_remove_db() { ynh_handle_getopts_args "$@" local psql_root_password=$(sudo cat $PSQL_ROOT_PWD_FILE) - if $(ynh_psql_database_exists "$db_name"); then # Check if the database exists + if ynh_psql_database_exists --database=$db_name; then # Check if the database exists echo "Removing database $db_name" >&2 ynh_psql_drop_db $db_name # Remove the database else @@ -231,7 +231,7 @@ ynh_psql_remove_db() { fi # Remove psql user if it exists - if $(ynh_psql_user_exists --user=$db_user); then + if ynh_psql_user_exists --user=$db_user; then echo "Removing user $db_user" >&2 ynh_psql_drop_user $db_user else From 9b8bd79a37655585e806d04ef96534c96caaf19b Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 21 Feb 2019 00:16:57 +0100 Subject: [PATCH 263/721] Don't use [[]] anymore --- data/helpers.d/psql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index f960e6297..bdc9a07c2 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -141,7 +141,7 @@ ynh_psql_user_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if [[ -z $(sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';") ]]; then + if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';" | grep --quiet "$user" ; then return 1 else return 0 @@ -160,7 +160,7 @@ ynh_psql_database_exists() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if [[ -z $(sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';") ]]; then + if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(sudo cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';" | grep --quiet "$user"; then return 1 else return 0 From 81bc9987bdb8b75414fabf8dfd54ce6f59e68ab1 Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 21 Feb 2019 01:03:10 +0100 Subject: [PATCH 264/721] rework ynh_psql_create_db --- data/helpers.d/psql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index bdc9a07c2..e427582d6 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -67,18 +67,18 @@ ynh_psql_execute_file_as_root() { # # [internal] # -# usage: ynh_psql_create_db db [user [pwd]] +# usage: ynh_psql_create_db db [user] # | arg: db - the database name to create # | arg: user - the user to grant privilegies -# | arg: pwd - the password to identify user by ynh_psql_create_db() { local db=$1 + local user=$2 local sql="CREATE DATABASE ${db};" # grant all privilegies to user - if [[ $# -gt 1 ]]; then - sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${2} WITH GRANT OPTION;" + if [ $# -gt 1 ]; then + sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" fi ynh_psql_execute_as_root --sql="$sql" From b1b14a399d0587258ecef91c8ac3dab2203cf6e8 Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 21 Feb 2019 01:03:21 +0100 Subject: [PATCH 265/721] fix ynh_psql_database_exists --- data/helpers.d/psql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index e427582d6..8051736c1 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -154,8 +154,8 @@ ynh_psql_user_exists() { # | arg: -d, --database - the database for which to check existence ynh_psql_database_exists() { # Declare an array to define the options of this helper. - local legacy_args=u - declare -Ar args_array=([u]=database=) + local legacy_args=d + declare -Ar args_array=([d]=database=) local database # Manage arguments with getopts ynh_handle_getopts_args "$@" From 95dd4303344aead0603dbfad765850820a37c4ce Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 21 Feb 2019 01:06:38 +0100 Subject: [PATCH 266/721] ynh_psql_create_db take only 2 arguments --- data/helpers.d/psql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 8051736c1..f5e076d4f 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -204,7 +204,7 @@ ynh_psql_setup_db() { ynh_psql_create_user "$db_user" "$db_pwd" fi - ynh_psql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database + ynh_psql_create_db "$db_name" "$db_user" # Create the database ynh_app_setting_set --app=$app --key=psqlpwd --value=$db_pwd # Store the password in the app's config } From 49ec93a9c57f5a3fd1c734c8da09967e2d1809ab Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 21 Feb 2019 01:31:31 +0100 Subject: [PATCH 267/721] default argument for a optional argument --- data/helpers.d/psql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index f5e076d4f..47804f585 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -72,7 +72,7 @@ ynh_psql_execute_file_as_root() { # | arg: user - the user to grant privilegies ynh_psql_create_db() { local db=$1 - local user=$2 + local user=${2:-} local sql="CREATE DATABASE ${db};" From c24d45beff554a7de94b0e9dcc560d0c66896a4d Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 21 Feb 2019 01:39:15 +0100 Subject: [PATCH 268/721] remove false promises --- data/helpers.d/psql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 47804f585..e3c3bb96e 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -120,7 +120,7 @@ ynh_psql_dump_db() { # # [internal] # -# usage: ynh_psql_create_user user pwd [host] +# usage: ynh_psql_create_user user pwd # | arg: user - the user name to create # | arg: pwd - the password to identify user by ynh_psql_create_user() { From dd89391f0174b58213fa209348723d01794016fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 21 Feb 2019 09:31:29 +0100 Subject: [PATCH 269/721] Add more explicit args for permissions --- src/yunohost/data_migrations/0009_setup_group_permission.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 2baa5f465..0a21c2dfe 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -56,7 +56,7 @@ def migrate_LDAP_db(auth): {'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']}) user_group_add(auth, username, gid=user_info['uidNumber'][0], sync_perm=False) user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) - user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=False) + user_group_update(auth, groupname='all_users', add_user=username, force=True, sync_perm=False) def migrate_app_permission(auth, app=None): @@ -79,7 +79,7 @@ def migrate_app_permission(auth, app=None): permission_add(auth, app, 'main', urls=[url], default_allow=True, sync_perm=False) if permission: allowed_group = permission.split(',') - user_permission_add(auth, [app], 'main', group=allowed_group, sync_perm=False) + user_permission_add(auth, [app], permission='main', group=allowed_group, sync_perm=False) app_setting(app, 'allowed_users', delete=True) From 81e1d163f4fc24a5352915ea33999a8f13dc9868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 21 Feb 2019 09:35:17 +0100 Subject: [PATCH 270/721] Add more explicit args for permissions --- src/yunohost/data_migrations/0009_setup_group_permission.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 0a21c2dfe..47d0d53a2 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -76,7 +76,7 @@ def migrate_app_permission(auth, app=None): url = None if domain and path: url = domain + path - permission_add(auth, app, 'main', urls=[url], default_allow=True, sync_perm=False) + permission_add(auth, app, permission='main', urls=[url], default_allow=True, sync_perm=False) if permission: allowed_group = permission.split(',') user_permission_add(auth, [app], permission='main', group=allowed_group, sync_perm=False) From c029ccb7ad80f415fb3831bf5fc9dce217d8ee97 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 21 Feb 2019 15:07:06 +0100 Subject: [PATCH 271/721] Also feed the setting name when calling setting hook --- src/yunohost/settings.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index cb98fa57e..81ea46114 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -264,7 +264,7 @@ def trigger_post_change_hook(setting_name, old_value, new_value): return f = post_change_hooks[setting_name] - f(old_value, new_value) + f(setting_name, old_value, new_value) # =========================================== @@ -273,14 +273,15 @@ def trigger_post_change_hook(setting_name, old_value, new_value): # You can define such an action with : # # @post_change_hook("your.setting.name") -# def some_function_name(old_value, new_value): +# def some_function_name(setting_name, old_value, new_value): # # Do some stuff # # =========================================== #@post_change_hook("example.int") -#def myfunc(old_value, new_value): +#def myfunc(setting_name, old_value, new_value): # print("In hook") +# print(setting_name) # print(old_value) # print(new_value) From 5c5330be3da369e1a255bb305009d35738927a8e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 21 Feb 2019 15:35:49 +0100 Subject: [PATCH 272/721] Issue happening with some weird app instance name ... --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0bca68787..302049ed8 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2345,6 +2345,7 @@ def _parse_app_instance_name(app_instance_name): True """ match = re_app_instance_name.match(app_instance_name) + assert match, "Could not parse app instance name : %s" % app_instance_name appid = match.groupdict().get('appid') app_instance_nb = int(match.groupdict().get('appinstancenb')) if match.groupdict().get('appinstancenb') is not None else 1 return (appid, app_instance_nb) From 9aa6cb6ac5c23f1a5385d0bda513629a445bf8ee Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 21 Feb 2019 20:46:18 +0100 Subject: [PATCH 273/721] add hook to reconfigure nginx or ssh on settings change --- src/yunohost/settings.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 1d60c3a6a..96c73a79d 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -239,3 +239,14 @@ def _save_settings(settings, location=SETTINGS_PATH): settings_fd.write(result) except Exception as e: raise YunohostError('global_settings_cant_write_settings', reason=e) + +@post_change_hook("security.ciphers.compatibility") +def reconfigure_nginx(setting_name, old_value, new_value): + if old_value != new_value: + service_regen_conf("nginx") + +@post_change_hook("service.ssh.ciphers.compatibility") +def reconfigure_ssh(setting_name, old_value, new_value): + if old_value != new_value: + service_regen_conf("ssh") + From ac250e2549974adc2a610756f9bea2f8404b27d5 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 21 Feb 2019 21:15:25 +0100 Subject: [PATCH 274/721] fix service_regen_conf syntax --- src/yunohost/settings.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 96c73a79d..d19b0cba3 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -7,6 +7,7 @@ from collections import OrderedDict from moulinette import m18n from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger +from yunohost.service import service_regen_conf logger = getActionLogger('yunohost.settings') @@ -243,10 +244,10 @@ def _save_settings(settings, location=SETTINGS_PATH): @post_change_hook("security.ciphers.compatibility") def reconfigure_nginx(setting_name, old_value, new_value): if old_value != new_value: - service_regen_conf("nginx") + service_regen_conf(names=['nginx'], force=True) @post_change_hook("service.ssh.ciphers.compatibility") def reconfigure_ssh(setting_name, old_value, new_value): if old_value != new_value: - service_regen_conf("ssh") + service_regen_conf(names=['ssh'], force=True) From bca2af3391a815873a7b63084d941640e0dbadf0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 21 Feb 2019 23:37:19 +0100 Subject: [PATCH 275/721] [microdecision] I'm sick of those people who end up with app repo being added as app list and messing up everything -.- --- src/yunohost/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 302049ed8..5f5d9f8f9 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -97,6 +97,9 @@ def app_fetchlist(url=None, name=None): name -- Name of the list url -- URL of remote JSON list """ + if not url.endswith(".json"): + raise YunohostError("This is not a valid application list url. It should end with .json.") + # If needed, create folder where actual appslists are stored if not os.path.exists(REPO_PATH): os.makedirs(REPO_PATH) From d7f381518348cde6feb6be8a64c510745d35180f Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 22 Feb 2019 00:58:12 +0100 Subject: [PATCH 276/721] For your eyes --- data/helpers.d/psql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index e3c3bb96e..1d992d268 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -77,7 +77,7 @@ ynh_psql_create_db() { local sql="CREATE DATABASE ${db};" # grant all privilegies to user - if [ $# -gt 1 ]; then + if [ -n "$user" ]; then sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" fi From abc091911c717539cb259a9a353159ba590cb017 Mon Sep 17 00:00:00 2001 From: Kayou Date: Fri, 22 Feb 2019 01:13:23 +0100 Subject: [PATCH 277/721] User ynh_replace_string, add postgresql in the admin panel --- data/helpers.d/psql | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 1d992d268..324cfee83 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -252,8 +252,10 @@ ynh_psql_test_if_first_run() { if [ -e /etc/postgresql/9.4/ ]; then local pg_hba=/etc/postgresql/9.4/main/pg_hba.conf + local logfile=/var/log/postgresql/postgresql-9.4-main.log elif [ -e /etc/postgresql/9.6/ ]; then local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf + local logfile=/var/log/postgresql/postgresql-9.6-main.log else ynh_die "postgresql shoud be 9.4 or 9.6" fi @@ -265,8 +267,11 @@ ynh_psql_test_if_first_run() { # https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html#EXAMPLE-PG-HBA.CONF # Note: we can't use peer since YunoHost create users with nologin # See: https://github.com/YunoHost/yunohost/blob/unstable/data/helpers.d/user - sed -i '/local\s*all\s*all\s*peer/i \ - local all all password' "$pg_hba" + ynh_replace_string --match_string="local\(\s*\)all\(\s*\)all\(\s*\)peer" --replace_string="local\1all\2all\3password" --target_file="$pg_hba" + + # Advertise service in admin panel + yunohost service add postgresql --log "$logfile" + systemctl enable postgresql systemctl reload postgresql fi From 7342f894e5e2087e97ba0a9c2595ccb103e6e6ce Mon Sep 17 00:00:00 2001 From: yalh76 Date: Fri, 22 Feb 2019 14:28:08 +0100 Subject: [PATCH 278/721] add the arch argument to ynh_install_nodejs CF https://github.com/YunoHost/issues/issues/1311#issuecomment-466379366 --- data/helpers.d/nodejs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index c4332b60c..9b16f5729 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -64,8 +64,9 @@ ynh_install_nodejs () { # Declare an array to define the options of this helper. local legacy_args=n - declare -Ar args_array=( [n]=nodejs_version= ) + declare -Ar args_array=( [n]=nodejs_version= [a]=arch=) local nodejs_version + local arch # Manage arguments with getopts ynh_handle_getopts_args "$@" @@ -98,7 +99,12 @@ ynh_install_nodejs () { test -x /usr/bin/npm_n && mv /usr/bin/npm_n /usr/bin/npm # Install the requested version of nodejs - n $nodejs_version + if [ -z "$arch" ] + then + n $nodejs_version + else + n $nodejs_version --arch=$arch + fi # Find the last "real" version for this major version of node. real_nodejs_version=$(find $node_version_path/$nodejs_version* -maxdepth 0 | sort --version-sort | tail --lines=1) From 53ba867f30349b953216302badf491e99a93dea4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 14:46:53 +0100 Subject: [PATCH 279/721] Also forbid to app_upgrade if dpkg is broken --- src/yunohost/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index d332971c4..05d5b1e4c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -564,6 +564,9 @@ def app_upgrade(auth, app=[], url=None, file=None): url -- Git url to fetch for upgrade """ + if packages.dpkg_is_broken(): + raise YunohostError(m18n.n("dpkg_is_broken")) + from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback # Retrieve interface From e7241394afb33b0ddb46531addff8727a17d3b4b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:00:18 +0100 Subject: [PATCH 280/721] Adding dpkg checks after removal of an application --- src/yunohost/app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 05d5b1e4c..61b9c69e4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -880,6 +880,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on app_ssowatconf(auth) + if packages.dpkg_is_broken(): + logger.error(m18n.n("this_action_broke_dpkg")) + if install_retcode == -1: msg = m18n.n('operation_interrupted') + " " + error_msg raise YunohostError(msg, raw_msg=True) @@ -962,6 +965,9 @@ def app_remove(operation_logger, auth, app): hook_remove(app) app_ssowatconf(auth) + if packages.dpkg_is_broken(): + raise YunohostError(m18n.n("this_action_broke_dpkg")) + def app_addaccess(auth, apps, users=[]): """ From c824f10cc29b98b551f042fde10a894f08791855 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:04:34 +0100 Subject: [PATCH 281/721] Also check for dpkg corruption before upgrading system or app... --- src/yunohost/tools.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index b2fbf380c..254e0533c 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -524,6 +524,10 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal ignore_packages -- Ignore APT packages upgrade """ + from yunohost.utils import packages + if packages.dpkg_is_broken(): + raise YunohostError(m18n.n("dpkg_is_broken")) + failure = False # Retrieve interface From dcf2ca8b550dae29e9085288bd0380e4ca463fd9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:06:11 +0100 Subject: [PATCH 282/721] Check dpkg status asap in app_install --- src/yunohost/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 61b9c69e4..159017c89 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -708,6 +708,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on no_remove_on_failure -- Debug option to avoid removing the app on a failed installation force -- Do not ask for confirmation when installing experimental / low-quality apps """ + if packages.dpkg_is_broken(): + raise YunohostError(m18n.n("dpkg_is_broken")) + from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger @@ -725,9 +728,6 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on }, } - if packages.dpkg_is_broken(): - raise YunohostError(m18n.n("dpkg_is_broken")) - def confirm_install(confirm): # Ignore if there's nothing for confirm (good quality app), if --force is used From ba7bdb8f142c0d94eeecb12ca9feb3ddc3759c0f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:07:09 +0100 Subject: [PATCH 283/721] No m18n.n needed in YunohostErrors --- src/yunohost/app.py | 6 +++--- src/yunohost/tools.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 159017c89..0bd8e412f 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -565,7 +565,7 @@ def app_upgrade(auth, app=[], url=None, file=None): """ if packages.dpkg_is_broken(): - raise YunohostError(m18n.n("dpkg_is_broken")) + raise YunohostError("dpkg_is_broken") from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback @@ -709,7 +709,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on force -- Do not ask for confirmation when installing experimental / low-quality apps """ if packages.dpkg_is_broken(): - raise YunohostError(m18n.n("dpkg_is_broken")) + raise YunohostError("dpkg_is_broken") from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.log import OperationLogger @@ -966,7 +966,7 @@ def app_remove(operation_logger, auth, app): app_ssowatconf(auth) if packages.dpkg_is_broken(): - raise YunohostError(m18n.n("this_action_broke_dpkg")) + raise YunohostError("this_action_broke_dpkg") def app_addaccess(auth, apps, users=[]): diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 254e0533c..9a6c7ccba 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -526,7 +526,7 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal """ from yunohost.utils import packages if packages.dpkg_is_broken(): - raise YunohostError(m18n.n("dpkg_is_broken")) + raise YunohostError("dpkg_is_broken") failure = False From 2fec4a6c6415b7ade56f3d0227d09b3018ee59d9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:13:59 +0100 Subject: [PATCH 284/721] Implement second message, this_action_broke_dpkg --- locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.json b/locales/en.json index 477563c8b..5c87106ec 100644 --- a/locales/en.json +++ b/locales/en.json @@ -464,6 +464,7 @@ "ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax", "system_upgraded": "The system has been upgraded", "system_username_exists": "Username already exists in the system users", + "this_action_broke_dpkg": "This action broke dpkg/apt (the system package managers)... You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "unbackup_app": "App '{app:s}' will not be saved", "unexpected_error": "An unexpected error occured: {error}", "unit_unknown": "Unknown unit '{unit:s}'", From 6fc6d6cfe6b6045de4251cb9dac01d2acae370f9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 15:16:07 +0100 Subject: [PATCH 285/721] Who knows, maybe this folder doesn't exist in some context --- src/yunohost/utils/packages.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 9cb3bd974..e10de6493 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -477,5 +477,7 @@ def dpkg_is_broken(): # If dpkg is broken, /var/lib/dpkg/updates # will contains files like 0001, 0002, ... # ref: https://sources.debian.org/src/apt/1.4.9/apt-pkg/deb/debsystem.cc/#L141-L174 + if not os.path.isdir("/var/lib/dpkg/updates/"): + return False return any(re.match("^[0-9]+$", f) for f in os.listdir("/var/lib/dpkg/updates/")) From 0a84e2f2261ba67d4cc9dc71b430576f80ec726d Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 22 Feb 2019 15:38:04 +0100 Subject: [PATCH 286/721] Default value for $arch --- data/helpers.d/nodejs | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 9b16f5729..f48685ece 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -69,6 +69,7 @@ ynh_install_nodejs () { local arch # Manage arguments with getopts ynh_handle_getopts_args "$@" + arch=${arch:-} # Create $n_install_dir mkdir -p "$n_install_dir" From 198919adc836c3f7041697c631275599dfc75d28 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Feb 2019 16:24:19 +0100 Subject: [PATCH 287/721] Don't add Strict-Transport-Security header in nginx conf if using a self-signed cert --- data/templates/nginx/server.tpl.conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 0c221f188..43d38ca98 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -51,7 +51,9 @@ server { # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners # https://wiki.mozilla.org/Security/Guidelines/Web_Security # https://observatory.mozilla.org/ - more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; + {% if domain_cert_ca != "Self-signed" %} + more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; + {% endif %} more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'"; more_set_headers "X-Content-Type-Options : nosniff"; From 81178e0841d81cf07da56826bda4c16403269a1c Mon Sep 17 00:00:00 2001 From: yalh76 Date: Fri, 22 Feb 2019 17:55:42 +0100 Subject: [PATCH 288/721] remove arguments and let ynh_install_nodejs manage the issue --- data/helpers.d/nodejs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index f48685ece..f277eeef0 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -64,12 +64,10 @@ ynh_install_nodejs () { # Declare an array to define the options of this helper. local legacy_args=n - declare -Ar args_array=( [n]=nodejs_version= [a]=arch=) + declare -Ar args_array=( [n]=nodejs_version= ) local nodejs_version - local arch # Manage arguments with getopts ynh_handle_getopts_args "$@" - arch=${arch:-} # Create $n_install_dir mkdir -p "$n_install_dir" @@ -100,11 +98,11 @@ ynh_install_nodejs () { test -x /usr/bin/npm_n && mv /usr/bin/npm_n /usr/bin/npm # Install the requested version of nodejs - if [ -z "$arch" ] + if [[ $uname =~ aarch64 || $uname =~ arm64]] then - n $nodejs_version + n $nodejs_version --arch=arm64 else - n $nodejs_version --arch=$arch + n $nodejs_version fi # Find the last "real" version for this major version of node. From dd287173087e772d45482c47644479c6a336e40d Mon Sep 17 00:00:00 2001 From: yalh76 Date: Sat, 23 Feb 2019 01:23:02 +0100 Subject: [PATCH 289/721] Fix $uname --- data/helpers.d/nodejs | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index f277eeef0..61a1414ef 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -98,6 +98,7 @@ ynh_install_nodejs () { test -x /usr/bin/npm_n && mv /usr/bin/npm_n /usr/bin/npm # Install the requested version of nodejs + uname=$(uname -m) if [[ $uname =~ aarch64 || $uname =~ arm64]] then n $nodejs_version --arch=arm64 From 283e01db8fa3c2322670f465427710cbbb300640 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Feb 2019 01:58:33 +0100 Subject: [PATCH 290/721] Don't make a whole HTTP request every dyndns update... --- src/yunohost/dyndns.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 2f8d63135..53486241a 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -333,7 +333,8 @@ def _guess_current_dyndns_domain(dyn_host): """ # Retrieve the first registered domain - for path in glob.iglob('/etc/yunohost/dyndns/K*.private'): + paths = list(glob.iglob('/etc/yunohost/dyndns/K*.private')) + for path in paths: match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path) if not match: match = RE_DYNDNS_PRIVATE_KEY_SHA512.match(path) @@ -343,7 +344,9 @@ def _guess_current_dyndns_domain(dyn_host): # Verify if domain is registered (i.e., if it's available, skip # current domain beause that's not the one we want to update..) - if _dyndns_available(dyn_host, _domain): + # If there's only 1 such key found, then avoid doing the request + # for nothing (that's very probably the one we want to find ...) + if len(paths) > 1 and _dyndns_available(dyn_host, _domain): continue else: return (_domain, path) From 3f8f328fae8967a6294495484c7c197e8497947d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Feb 2019 02:04:41 +0100 Subject: [PATCH 291/721] Delete the key if subscribing failed, to avoid confusion later trying to detect registered domains --- src/yunohost/dyndns.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 53486241a..090ee3901 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -144,7 +144,8 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom 'dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER %s' % domain) os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private') - key_file = glob.glob('/etc/yunohost/dyndns/*.key')[0] + private_file = glob.glob('/etc/yunohost/dyndns/*%s*.private' % domain)[0] + key_file = glob.glob('/etc/yunohost/dyndns/*%s*.key' % domain)[0] with open(key_file) as f: key = f.readline().strip().split(' ', 6)[-1] @@ -153,8 +154,12 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom try: r = requests.post('https://%s/key/%s?key_algo=hmac-sha512' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}, timeout=30) except requests.ConnectionError: + os.system("rm %s" % private_file) + os.system("rm %s" % key_file) raise YunohostError('no_internet_connection') if r.status_code != 201: + os.system("rm %s" % private_file) + os.system("rm %s" % key_file) try: error = json.loads(r.text)['error'] except: From 9657387746137d0a4e1adfefd2757759325951a7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Feb 2019 02:10:24 +0100 Subject: [PATCH 292/721] Prevent calling dyndns_subscribe if already subscribed --- src/yunohost/dyndns.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 090ee3901..36509b0e4 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -119,6 +119,9 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom subscribe_host -- Dynette HTTP API to subscribe to """ + if len(glob.glob('/etc/yunohost/dyndns/*.key')) != 0 or os.path.exists('/etc/cron.d/yunohost-dyndns'): + raise YunohostError('domain_dyndns_already_subscribed') + if domain is None: domain = _get_maindomain() operation_logger.related_to.append(('domain', domain)) From f17f51f3f6de24c69eaf552209947a7414cb5b12 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 24 Feb 2019 03:32:14 +0100 Subject: [PATCH 293/721] Add ynh_app_setting helper to optimize app setting interface --- data/helpers.d/setting | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 6f75f6c80..18a57780a 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -14,7 +14,7 @@ ynh_app_setting_get() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost app setting "$app" "$key" --output-as plain --quiet + ynh_app_setting "get" "$app" "$key" } # Set an application setting @@ -33,7 +33,7 @@ ynh_app_setting_set() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost app setting "$app" "$key" --value="$value" --quiet + ynh_app_setting "set" "$app" "$key" "$value" } # Delete an application setting @@ -50,5 +50,36 @@ ynh_app_setting_delete() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - sudo yunohost app setting -d "$app" "$key" --quiet + ynh_app_setting "delete" "$app" "$key" +} + +# Small "hard-coded" interface to avoid calling "yunohost app" directly each +# time dealing with a setting is needed (which may be so slow on ARM boards) +# +# [internal] +# +ynh_app_setting() +{ + ACTION="$1" APP="$2" KEY="$3" VALUE="$4" python - < Date: Mon, 25 Feb 2019 01:23:05 +0100 Subject: [PATCH 294/721] Typo --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index ac9091c19..a6d95ada1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -211,7 +211,7 @@ "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", - "hook_json_return_error": "Faild to read return from hook {path:s}. Error: {msg:s}", + "hook_json_return_error": "Failed to read return from hook {path:s}. Error: {msg:s}", "hook_list_by_invalid": "Invalid property to list hook by", "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation complete", From e7d322b68ccf6bb13e103ace8b1ed81543984f91 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Mon, 25 Feb 2019 18:29:46 +0100 Subject: [PATCH 295/721] don't enforce services reconfiguration generation unless you don't want to keep user meodifications --- src/yunohost/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index cb87f67ac..916e8b8c3 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -286,9 +286,9 @@ def trigger_post_change_hook(setting_name, old_value, new_value): @post_change_hook("security.ciphers.compatibility") def reconfigure_nginx(setting_name, old_value, new_value): if old_value != new_value: - service_regen_conf(names=['nginx'], force=True) + service_regen_conf(names=['nginx']) @post_change_hook("service.ssh.ciphers.compatibility") def reconfigure_ssh(setting_name, old_value, new_value): if old_value != new_value: - service_regen_conf(names=['ssh'], force=True) + service_regen_conf(names=['ssh']) From 873bf4ae370cf785c66db0028ca1ab5bc06340c0 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 25 Feb 2019 18:44:21 +0100 Subject: [PATCH 296/721] Add min version for all helpers --- data/helpers.d/backend | 16 ++++++++++++++++ data/helpers.d/debug | 4 ++++ data/helpers.d/filesystem | 11 +++++++++++ data/helpers.d/getopts | 2 ++ data/helpers.d/ip | 3 +++ data/helpers.d/mysql | 24 ++++++++++++++++++++++++ data/helpers.d/network | 8 ++++++++ data/helpers.d/nodejs | 10 ++++++++++ data/helpers.d/package | 24 ++++++++++++++++++++++++ data/helpers.d/print | 27 +++++++++++++++++++++++++++ data/helpers.d/psql | 20 ++++++++++++++++++++ data/helpers.d/setting | 6 ++++++ data/helpers.d/string | 6 ++++++ data/helpers.d/system | 12 ++++++++++++ data/helpers.d/user | 12 ++++++++++++ data/helpers.d/utils | 8 ++++++++ 16 files changed, 193 insertions(+) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index e710da9c7..ac6f3e5de 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -14,6 +14,8 @@ # # It's possible to use this helper several times, each config will be added to the same logrotate config file. # Unless you use the option --non-append +# +# Requires YunoHost version 2.6.4 or higher. ynh_use_logrotate () { # Declare an array to define the options of this helper. local legacy_args=lnuya @@ -92,6 +94,8 @@ EOF # Remove the app's logrotate config. # # usage: ynh_remove_logrotate +# +# Requires YunoHost version 2.6.4 or higher. ynh_remove_logrotate () { if [ -e "/etc/logrotate.d/$app" ]; then sudo rm "/etc/logrotate.d/$app" @@ -112,6 +116,7 @@ ynh_remove_logrotate () { # __APP__ by $app # __FINALPATH__ by $final_path # +# Requires YunoHost version 2.7.2 or higher. ynh_add_systemd_config () { # Declare an array to define the options of this helper. local legacy_args=st @@ -147,6 +152,7 @@ ynh_add_systemd_config () { # usage: ynh_remove_systemd_config [--service=service] # | arg: -s, --service - Service name (optionnal, $app by default) # +# Requires YunoHost version 2.7.2 or higher. ynh_remove_systemd_config () { # Declare an array to define the options of this helper. local legacy_args=s @@ -183,6 +189,7 @@ ynh_remove_systemd_config () { # __PATH_2__ by $path_2 # __PORT_2__ by $port_2 # +# Requires YunoHost version 2.7.2 or higher. ynh_add_nginx_config () { finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" local others_var=${1:-} @@ -233,6 +240,8 @@ ynh_add_nginx_config () { # Remove the dedicated nginx config # # usage: ynh_remove_nginx_config +# +# Requires YunoHost version 2.7.2 or higher. ynh_remove_nginx_config () { ynh_secure_remove --file="/etc/nginx/conf.d/$domain.d/$app.conf" sudo systemctl reload nginx @@ -241,6 +250,8 @@ ynh_remove_nginx_config () { # Create a dedicated php-fpm config # # usage: ynh_add_fpm_config +# +# Requires YunoHost version 2.7.2 or higher. ynh_add_fpm_config () { # Configure PHP-FPM 7.0 by default local fpm_config_dir="/etc/php/7.0/fpm" @@ -276,6 +287,8 @@ ynh_add_fpm_config () { # Remove the dedicated php-fpm config # # usage: ynh_remove_fpm_config +# +# Requires YunoHost version 2.7.2 or higher. ynh_remove_fpm_config () { local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service) @@ -350,6 +363,7 @@ ynh_remove_fpm_config () { # To validate your regex you can test with this command: # fail2ban-regex /var/log/YOUR_LOG_FILE_PATH /etc/fail2ban/filter.d/YOUR_APP.conf # +# Requires YunoHost version 3.?.? or higher. ynh_add_fail2ban_config () { # Declare an array to define the options of this helper. declare -Ar args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) @@ -429,6 +443,8 @@ EOF # Remove the dedicated fail2ban config (jail and filter conf files) # # usage: ynh_remove_fail2ban_config +# +# Requires YunoHost version 3.?.? or higher. ynh_remove_fail2ban_config () { ynh_secure_remove "/etc/fail2ban/jail.d/$app.conf" ynh_secure_remove "/etc/fail2ban/filter.d/$app.conf" diff --git a/data/helpers.d/debug b/data/helpers.d/debug index a8b7c8d69..ea20ffc1a 100644 --- a/data/helpers.d/debug +++ b/data/helpers.d/debug @@ -5,6 +5,8 @@ # usage: ynh_debug [--message=message] [--trace=1/0] # | arg: -m, --message= - The text to print # | arg: -t, --trace= - Turn on or off the trace of the script. Usefull to trace nonly a small part of a script. +# +# Requires YunoHost version 3.?.? or higher. ynh_debug () { # Disable set xtrace for the helper itself, to not pollute the debug log set +x @@ -54,6 +56,8 @@ ynh_debug () { # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. # # | arg: command - command to execute +# +# Requires YunoHost version 3.?.? or higher. ynh_debug_exec () { ynh_debug --message="$(eval $@)" } diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 10123dea4..6fb6347a6 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -46,6 +46,7 @@ CAN_BIND=${CAN_BIND:-1} # ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "/conf/" # # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/$app.conf" # +# Requires YunoHost version 2.4.0 or higher. ynh_backup() { # TODO find a way to avoid injection by file strange naming ! @@ -158,6 +159,7 @@ ynh_backup() { # # usage: ynh_restore # +# Requires YunoHost version 2.6.4 or higher. ynh_restore () { # Deduce the relative path of $YNH_CWD local REL_DIR="${YNH_CWD#$YNH_BACKUP_DIR/}" @@ -219,6 +221,7 @@ with open(sys.argv[1], 'r') as backup_file: # # DON'T GIVE THE ARCHIVE PATH: # ynh_restore_file "conf/nginx.conf" # +# Requires YunoHost version 2.6.4 or higher. ynh_restore_file () { # Declare an array to define the options of this helper. local legacy_args=odm @@ -313,6 +316,8 @@ properly with chmod/chown." >&2 # # usage: ynh_store_file_checksum --file=file # | arg: -f, --file - The file on which the checksum will performed, then stored. +# +# Requires YunoHost version 2.6.4 or higher. ynh_store_file_checksum () { # Declare an array to define the options of this helper. local legacy_args=f @@ -345,6 +350,8 @@ ynh_store_file_checksum () { # | arg: -f, --file - The file on which the checksum test will be perfomed. # # | ret: Return the name a the backup file, or nothing +# +# Requires YunoHost version 2.6.4 or higher. ynh_backup_if_checksum_is_different () { # Declare an array to define the options of this helper. local legacy_args=f @@ -376,6 +383,8 @@ ynh_backup_if_checksum_is_different () { # # usage: ynh_remove_file_checksum file # | arg: -f, --file= - The file for which the checksum will be deleted +# +# Requires YunoHost version 3.3.1 or higher. ynh_delete_file_checksum () { # Declare an array to define the options of this helper. local legacy_args=f @@ -392,6 +401,8 @@ ynh_delete_file_checksum () { # # usage: ynh_secure_remove --file=path_to_remove # | arg: -f, --file - File or directory to remove +# +# Requires YunoHost version 2.6.4 or higher. ynh_secure_remove () { # Declare an array to define the options of this helper. local legacy_args=f diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index 7055325f1..f89784578 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -43,6 +43,8 @@ # To keep a retrocompatibility, a package can still call a helper, using getopts, with positional arguments. # The "legacy mode" will manage the positional arguments and fill the variable in the same order than they are given in $args_array. # e.g. for `my_helper "val1" val2`, arg1 will be filled with val1, and arg2 with val2. +# +# Requires YunoHost version 3.2.2 or higher. ynh_handle_getopts_args () { # Manage arguments only if there's some provided set +x diff --git a/data/helpers.d/ip b/data/helpers.d/ip index c50d8be73..2ca4053d9 100644 --- a/data/helpers.d/ip +++ b/data/helpers.d/ip @@ -7,6 +7,7 @@ # # example: ynh_validate_ip 4 111.222.333.444 # +# Requires YunoHost version 2.2.4 or higher. ynh_validate_ip() { # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298 @@ -40,6 +41,7 @@ EOF # usage: ynh_validate_ip4 --ip_address=ip_address # | ret: 0 for valid ipv4 addresses, 1 otherwise # +# Requires YunoHost version 2.2.4 or higher. ynh_validate_ip4() { # Declare an array to define the options of this helper. @@ -60,6 +62,7 @@ ynh_validate_ip4() # usage: ynh_validate_ip6 --ip_address=ip_address # | ret: 0 for valid ipv6 addresses, 1 otherwise # +# Requires YunoHost version 2.2.4 or higher. ynh_validate_ip6() { # Declare an array to define the options of this helper. diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index fa1a61dab..313b7a245 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -11,6 +11,8 @@ MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql # | arg: -u, --user - the user name to connect as # | arg: -p, --password - the user password # | arg: -d, --database - the database to connect to +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_connect_as() { # Declare an array to define the options of this helper. local legacy_args=upd @@ -30,6 +32,8 @@ ynh_mysql_connect_as() { # usage: ynh_mysql_execute_as_root --sql=sql [--database=database] # | arg: -s, --sql - the SQL command to execute # | arg: -d, --database - the database to connect to +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_execute_as_root() { # Declare an array to define the options of this helper. local legacy_args=sd @@ -49,6 +53,8 @@ ynh_mysql_execute_as_root() { # usage: ynh_mysql_execute_file_as_root --file=file [--database=database] # | arg: -f, --file - the file containing SQL commands # | arg: -d, --database - the database to connect to +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_execute_file_as_root() { # Declare an array to define the options of this helper. local legacy_args=fd @@ -71,6 +77,8 @@ ynh_mysql_execute_file_as_root() { # | arg: db - the database name to create # | arg: user - the user to grant privilegies # | arg: pwd - the password to identify user by +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_create_db() { local db=$1 @@ -95,6 +103,8 @@ ynh_mysql_create_db() { # # usage: ynh_mysql_drop_db db # | arg: db - the database name to drop +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_drop_db() { ynh_mysql_execute_as_root --sql="DROP DATABASE ${1};" } @@ -106,6 +116,8 @@ ynh_mysql_drop_db() { # usage: ynh_mysql_dump_db --database=database # | arg: -d, --database - the database name to dump # | ret: the mysqldump output +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_dump_db() { # Declare an array to define the options of this helper. local legacy_args=d @@ -124,6 +136,8 @@ ynh_mysql_dump_db() { # usage: ynh_mysql_create_user user pwd [host] # | arg: user - the user name to create # | arg: pwd - the password to identify user by +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_create_user() { ynh_mysql_execute_as_root \ --sql="CREATE USER '${1}'@'localhost' IDENTIFIED BY '${2}';" @@ -133,6 +147,8 @@ ynh_mysql_create_user() { # # usage: ynh_mysql_user_exists --user=user # | arg: -u, --user - the user for which to check existence +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_user_exists() { # Declare an array to define the options of this helper. @@ -156,6 +172,8 @@ ynh_mysql_user_exists() # # usage: ynh_mysql_drop_user user # | arg: user - the user name to drop +# +# Requires YunoHost version 2.2.4 or higher. ynh_mysql_drop_user() { ynh_mysql_execute_as_root --sql="DROP USER '${1}'@'localhost';" } @@ -169,6 +187,8 @@ ynh_mysql_drop_user() { # | arg: -u, --db_user - Owner of the database # | arg: -n, --db_name - Name of the database # | arg: -p, --db_pwd - Password of the database. If not given, a password will be generated +# +# Requires YunoHost version 2.6.4 or higher. ynh_mysql_setup_db () { # Declare an array to define the options of this helper. local legacy_args=unp @@ -192,6 +212,8 @@ ynh_mysql_setup_db () { # usage: ynh_mysql_remove_db --db_user=user --db_name=name # | arg: -u, --db_user - Owner of the database # | arg: -n, --db_name - Name of the database +# +# Requires YunoHost version 2.6.4 or higher. ynh_mysql_remove_db () { # Declare an array to define the options of this helper. local legacy_args=un @@ -223,6 +245,8 @@ ynh_mysql_remove_db () { # usage: ynh_sanitize_dbid --db_name=name # | arg: -n, --db_name - name to correct/sanitize # | ret: the corrected name +# +# Requires YunoHost version 2.2.4 or higher. ynh_sanitize_dbid () { # Declare an array to define the options of this helper. local legacy_args=n diff --git a/data/helpers.d/network b/data/helpers.d/network index a765d6346..8812f8f39 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -12,6 +12,8 @@ # # usage: ynh_normalize_url_path --path_url=path_to_normalize # | arg: -p, --path_url - URL path to normalize before using it +# +# Requires YunoHost version 2.6.4 or higher. ynh_normalize_url_path () { # Declare an array to define the options of this helper. local legacy_args=p @@ -36,6 +38,8 @@ ynh_normalize_url_path () { # # usage: ynh_find_port --port=begin_port # | arg: -p, --port - port to start to search +# +# Requires YunoHost version 2.6.4 or higher. ynh_find_port () { # Declare an array to define the options of this helper. local legacy_args=p @@ -59,6 +63,8 @@ ynh_find_port () { # usage: ynh_webpath_available --domain=domain --path_url=path # | arg: -d, --domain - the domain/host of the url # | arg: -p, --path_url - the web path to check the availability of +# +# Requires YunoHost version 2.6.4 or higher. ynh_webpath_available () { # Declare an array to define the options of this helper. local legacy_args=dp @@ -79,6 +85,8 @@ ynh_webpath_available () { # | arg: -a, --app - the app for which the domain should be registered # | arg: -d, --domain - the domain/host of the web path # | arg: -p, --path_url - the web path to be registered +# +# Requires YunoHost version 2.6.4 or higher. ynh_webpath_register () { # Declare an array to define the options of this helper. local legacy_args=adp diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 61a1414ef..34583328d 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -10,6 +10,8 @@ export N_PREFIX="$n_install_dir" # [internal] # # usage: ynh_install_n +# +# Requires YunoHost version 2.7.12 or higher. ynh_install_n () { echo "Installation of N - Node.js version management" >&2 # Build an app.src for n @@ -36,6 +38,8 @@ SOURCE_SUM=2ba3c9d4dd3c7e38885b37e02337906a1ee91febe6d5c9159d89a9050f2eea8f" > " # That's means it has to be added to any systemd script. # # usage: ynh_use_nodejs +# +# Requires YunoHost version 2.7.12 or higher. ynh_use_nodejs () { nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) @@ -59,6 +63,8 @@ ynh_use_nodejs () { # | arg: -n, --nodejs_version - Version of node to install. # If possible, prefer to use major version number (e.g. 8 instead of 8.10.0). # The crontab will handle the update of minor versions when needed. +# +# Requires YunoHost version 2.7.12 or higher. ynh_install_nodejs () { # Use n, https://github.com/tj/n to manage the nodejs versions @@ -135,6 +141,8 @@ ynh_install_nodejs () { # If no other app uses node, n will be also removed. # # usage: ynh_remove_nodejs +# +# Requires YunoHost version 2.7.12 or higher. ynh_remove_nodejs () { nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) @@ -164,6 +172,8 @@ ynh_remove_nodejs () { # This cron will check and update all minor node versions used by your apps. # # usage: ynh_cron_upgrade_node +# +# Requires YunoHost version 2.7.12 or higher. ynh_cron_upgrade_node () { # Build the update script cat > "$n_install_dir/node_update.sh" << EOF diff --git a/data/helpers.d/package b/data/helpers.d/package index 3924fc14e..75323521d 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -5,6 +5,8 @@ # [internal] # # usage: ynh_wait_dpkg_free +# +# Requires YunoHost version 3.3.1 or higher. ynh_wait_dpkg_free() { local try # With seq 1 17, timeout will be almost 30 minutes @@ -44,6 +46,8 @@ ynh_wait_dpkg_free() { # # usage: ynh_package_is_installed --package=name # | arg: -p, --package - the package name to check +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_is_installed() { # Declare an array to define the options of this helper. local legacy_args=p @@ -64,6 +68,8 @@ ynh_package_is_installed() { # usage: ynh_package_version --package=name # | arg: -p, --package - the package name to get version # | ret: the version or an empty string +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_version() { # Declare an array to define the options of this helper. local legacy_args=p @@ -84,6 +90,8 @@ ynh_package_version() { # [internal] # # usage: ynh_apt update +# +# Requires YunoHost version 2.4.0.3 or higher. ynh_apt() { ynh_wait_dpkg_free DEBIAN_FRONTEND=noninteractive apt-get -y $@ @@ -92,6 +100,8 @@ ynh_apt() { # Update package index files # # usage: ynh_package_update +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_update() { ynh_apt update } @@ -100,6 +110,8 @@ ynh_package_update() { # # usage: ynh_package_install name [name [...]] # | arg: name - the package name to install +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_install() { ynh_apt --no-remove -o Dpkg::Options::=--force-confdef \ -o Dpkg::Options::=--force-confold install $@ @@ -109,6 +121,8 @@ ynh_package_install() { # # usage: ynh_package_remove name [name [...]] # | arg: name - the package name to remove +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_remove() { ynh_apt remove $@ } @@ -117,6 +131,8 @@ ynh_package_remove() { # # usage: ynh_package_autoremove name [name [...]] # | arg: name - the package name to remove +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_autoremove() { ynh_apt autoremove $@ } @@ -125,6 +141,8 @@ ynh_package_autoremove() { # # usage: ynh_package_autopurge name [name [...]] # | arg: name - the package name to autoremove and purge +# +# Requires YunoHost version 2.7.2 or higher. ynh_package_autopurge() { ynh_apt autoremove --purge $@ } @@ -139,6 +157,8 @@ ynh_package_autopurge() { # # usage: ynh_package_install_from_equivs controlfile # | arg: controlfile - path of the equivs control file +# +# Requires YunoHost version 2.2.4 or higher. ynh_package_install_from_equivs () { local controlfile=$1 @@ -181,6 +201,8 @@ ynh_package_install_from_equivs () { # You can give a choice between some package with this syntax : "dep1|dep2" # Example : ynh_install_app_dependencies dep1 dep2 "dep3|dep4|dep5" # This mean in the dependence tree : dep1 & dep2 & (dep3 | dep4 | dep5) +# +# Requires YunoHost version 2.6.4 or higher. ynh_install_app_dependencies () { local dependencies=$@ local dependencies=${dependencies// /, } @@ -217,6 +239,8 @@ EOF # Dependencies will removed only if no other package need them. # # usage: ynh_remove_app_dependencies +# +# Requires YunoHost version 2.6.4 or higher. ynh_remove_app_dependencies () { local dep_app=${app//_/-} # Replace all '_' by '-' ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used. diff --git a/data/helpers.d/print b/data/helpers.d/print index 7f37021ae..6e7b2b1d7 100644 --- a/data/helpers.d/print +++ b/data/helpers.d/print @@ -2,6 +2,8 @@ # Print a message to stderr and exit # usage: ynh_die --message=MSG [--ret_code=RETCODE] +# +# Requires YunoHost version 2.4.0 or higher. ynh_die() { # Declare an array to define the options of this helper. local legacy_args=mc @@ -18,6 +20,8 @@ ynh_die() { # Display a message in the 'INFO' logging category # # usage: ynh_print_info --message="Some message" +# +# Requires YunoHost version 3.2.0 or higher. ynh_print_info() { # Declare an array to define the options of this helper. local legacy_args=m @@ -37,6 +41,8 @@ ynh_print_info() { # # Simply duplicate the log, execute the yunohost command and replace the log without the result of this command # It's a very badly hack... +# +# Requires YunoHost version 2.6.4 or higher. ynh_no_log() { local ynh_cli_log=/var/log/yunohost/yunohost-cli.log sudo cp -a ${ynh_cli_log} ${ynh_cli_log}-move @@ -50,6 +56,7 @@ ynh_no_log() { # # [internal] # +# Requires YunoHost version 3.2.0 or higher. ynh_print_log () { echo -e "${1}" } @@ -58,6 +65,8 @@ ynh_print_log () { # # usage: ynh_print_warn --message="Text to print" # | arg: -m, --message - The text to print +# +# Requires YunoHost version 3.2.0 or higher. ynh_print_warn () { # Declare an array to define the options of this helper. local legacy_args=m @@ -73,6 +82,8 @@ ynh_print_warn () { # # usage: ynh_print_err --message="Text to print" # | arg: -m, --message - The text to print +# +# Requires YunoHost version 3.2.0 or higher. ynh_print_err () { # Declare an array to define the options of this helper. local legacy_args=m @@ -91,6 +102,8 @@ ynh_print_err () { # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. # # | arg: command - command to execute +# +# Requires YunoHost version 3.2.0 or higher. ynh_exec_err () { ynh_print_err "$(eval $@)" } @@ -102,6 +115,8 @@ ynh_exec_err () { # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. # # | arg: command - command to execute +# +# Requires YunoHost version 3.2.0 or higher. ynh_exec_warn () { ynh_print_warn "$(eval $@)" } @@ -113,6 +128,8 @@ ynh_exec_warn () { # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. # # | arg: command - command to execute +# +# Requires YunoHost version 3.2.0 or higher. ynh_exec_warn_less () { eval $@ 2>&1 } @@ -124,6 +141,8 @@ ynh_exec_warn_less () { # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. # # | arg: command - command to execute +# +# Requires YunoHost version 3.2.0 or higher. ynh_exec_quiet () { eval $@ > /dev/null } @@ -135,6 +154,8 @@ ynh_exec_quiet () { # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. # # | arg: command - command to execute +# +# Requires YunoHost version 3.2.0 or higher. ynh_exec_fully_quiet () { eval $@ > /dev/null 2>&1 } @@ -143,6 +164,8 @@ ynh_exec_fully_quiet () { # # usage: ynh_print_OFF # WARNING: You should be careful with this helper, and never forget to use ynh_print_ON as soon as possible to restore the logging. +# +# Requires YunoHost version 3.2.0 or higher. ynh_print_OFF () { set +x } @@ -150,6 +173,8 @@ ynh_print_OFF () { # Restore the logging after ynh_print_OFF # # usage: ynh_print_ON +# +# Requires YunoHost version 3.2.0 or higher. ynh_print_ON () { set -x # Print an echo only for the log, to be able to know that ynh_print_ON has been called. @@ -163,6 +188,8 @@ ynh_print_ON () { # | arg: -w, --weight= - The weight for this progression. This value is 1 by default. Use a bigger value for a longer part of the script. # | arg: -t, --time= - Print the execution time since the last call to this helper. Especially usefull to define weights. # | arg: -l, --last= - Use for the last call of the helper, to fill te progression bar. +# +# Requires YunoHost version 3.?.? or higher. increment_progression=0 previous_weight=0 # Define base_time when the file is sourced diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 2ef13482a..70ea58af4 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -2,6 +2,8 @@ # Please always call this script in install and restore scripts # # usage: ynh_psql_test_if_first_run +# +# Requires YunoHost version 3.?.? or higher. ynh_psql_test_if_first_run() { if [ -f /etc/yunohost/psql ]; then @@ -43,6 +45,8 @@ ynh_psql_test_if_first_run() { # | arg: user - the user name to connect as # | arg: pwd - the user password # | arg: db - the database to connect to +# +# Requires YunoHost version 3.?.? or higher. ynh_psql_connect_as() { local user="$1" local pwd="$2" @@ -54,6 +58,8 @@ ynh_psql_connect_as() { # # usage: ynh_psql_execute_as_root sql [db] # | arg: sql - the SQL command to execute +# +# Requires YunoHost version 3.?.? or higher. ynh_psql_execute_as_root () { local sql="$1" sudo --login --user=postgres psql <<< "$sql" @@ -64,6 +70,8 @@ ynh_psql_execute_as_root () { # usage: ynh_psql_execute_file_as_root file [db] # | arg: file - the file containing SQL commands # | arg: db - the database to connect to +# +# Requires YunoHost version 3.?.? or higher. ynh_psql_execute_file_as_root() { local file="$1" local db="$2" @@ -79,6 +87,8 @@ ynh_psql_execute_file_as_root() { # | arg: user - Owner of the database # | arg: name - Name of the database # | arg: pwd - Password of the database. If not given, a password will be generated +# +# Requires YunoHost version 3.?.? or higher. ynh_psql_setup_db () { local db_user="$1" local db_name="$2" @@ -95,6 +105,8 @@ ynh_psql_setup_db () { # | arg: db - the database name to create # | arg: user - the user to grant privilegies # | arg: pwd - the user password +# +# Requires YunoHost version 3.?.? or higher. ynh_psql_create_db() { local db="$1" local user="$2" @@ -108,6 +120,8 @@ ynh_psql_create_db() { # usage: ynh_psql_drop_db db # | arg: db - the database name to drop # | arg: user - the user to drop +# +# Requires YunoHost version 3.?.? or higher. ynh_psql_remove_db() { local db="$1" local user="$2" @@ -122,6 +136,8 @@ ynh_psql_remove_db() { # usage: ynh_psql_dump_db db # | arg: db - the database name to dump # | ret: the psqldump output +# +# Requires YunoHost version 3.?.? or higher. ynh_psql_dump_db() { local db="$1" sudo --login --user=postgres pg_dump "$db" @@ -132,6 +148,8 @@ ynh_psql_dump_db() { # # usage: ynh_psql_create_user user pwd [host] # | arg: user - the user name to create +# +# Requires YunoHost version 3.?.? or higher. ynh_psql_create_user() { local user="$1" local pwd="$2" @@ -142,6 +160,8 @@ ynh_psql_create_user() { # # usage: ynh_psql_drop_user user # | arg: user - the user name to drop +# +# Requires YunoHost version 3.?.? or higher. ynh_psql_drop_user() { local user="$1" sudo --login --user=postgres dropuser "$user" diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 6f75f6c80..0c3698061 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -5,6 +5,8 @@ # usage: ynh_app_setting_get --app=app --key=key # | arg: -a, --app - the application id # | arg: -k, --key - the setting to get +# +# Requires YunoHost version 2.2.4 or higher. ynh_app_setting_get() { # Declare an array to define the options of this helper. local legacy_args=ak @@ -23,6 +25,8 @@ ynh_app_setting_get() { # | arg: -a, --app - the application id # | arg: -k, --key - the setting name to set # | arg: -v, --value - the setting value to set +# +# Requires YunoHost version 2.2.4 or higher. ynh_app_setting_set() { # Declare an array to define the options of this helper. local legacy_args=akv @@ -41,6 +45,8 @@ ynh_app_setting_set() { # usage: ynh_app_setting_delete --app=app --key=key # | arg: -a, --app - the application id # | arg: -k, --key - the setting to delete +# +# Requires YunoHost version 2.2.4 or higher. ynh_app_setting_delete() { # Declare an array to define the options of this helper. local legacy_args=ak diff --git a/data/helpers.d/string b/data/helpers.d/string index 739757d43..52eede872 100644 --- a/data/helpers.d/string +++ b/data/helpers.d/string @@ -6,6 +6,8 @@ # # usage: ynh_string_random [--length=string_length] # | arg: -l, --length - the string length to generate (default: 24) +# +# Requires YunoHost version 2.2.4 or higher. ynh_string_random() { # Declare an array to define the options of this helper. local legacy_args=l @@ -30,6 +32,8 @@ ynh_string_random() { # As this helper is based on sed command, regular expressions and # references to sub-expressions can be used # (see sed manual page for more information) +# +# Requires YunoHost version 2.6.4 or higher. ynh_replace_string () { # Declare an array to define the options of this helper. local legacy_args=mrf @@ -57,6 +61,8 @@ ynh_replace_string () { # # This helper will use ynh_replace_string, but as you can use special # characters, you can't use some regular expressions and sub-expressions. +# +# Requires YunoHost version 2.7.7 or higher. ynh_replace_special_string () { # Declare an array to define the options of this helper. local legacy_args=mrf diff --git a/data/helpers.d/system b/data/helpers.d/system index 9a4219e11..fd5b21435 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -16,6 +16,7 @@ # # It prints a warning to inform that the script was failed, and execute the ynh_clean_setup function if used in the app script # +# Requires YunoHost version 2.6.4 or higher. ynh_exit_properly () { local exit_code=$? if [ "$exit_code" -eq 0 ]; then @@ -43,6 +44,7 @@ ynh_exit_properly () { # immediately and a call to `ynh_clean_setup` is triggered if it has been # defined by your script. # +# Requires YunoHost version 2.6.4 or higher. ynh_abort_if_errors () { set -eu # Exit if a command fail, and if a variable is used unset. trap ynh_exit_properly EXIT # Capturing exit signals on shell script @@ -52,6 +54,8 @@ ynh_abort_if_errors () { # # usage: ynh_get_debian_release # | ret: The Debian release codename (i.e. jessie, stretch, ...) +# +# Requires YunoHost version 2.7.12 or higher. ynh_get_debian_release () { echo $(lsb_release --codename --short) } @@ -61,6 +65,8 @@ ynh_get_debian_release () { # usage: ynh_read_manifest manifest key # | arg: -m, --manifest= - Path of the manifest to read # | arg: -k, --key= - Name of the key to find +# +# Requires YunoHost version 3.?.? or higher. ynh_read_manifest () { # Declare an array to define the options of this helper. declare -Ar args_array=( [m]=manifest= [k]=manifest_key= ) @@ -85,6 +91,8 @@ ynh_read_manifest () { # # usage: ynh_app_upstream_version [-m manifest] # | arg: -m, --manifest= - Path of the manifest to read +# +# Requires YunoHost version 3.?.? or higher. ynh_app_upstream_version () { declare -Ar args_array=( [m]=manifest= ) local manifest @@ -104,6 +112,8 @@ ynh_app_upstream_version () { # # usage: ynh_app_package_version [-m manifest] # | arg: -m, --manifest= - Path of the manifest to read +# +# Requires YunoHost version 3.?.? or higher. ynh_app_package_version () { declare -Ar args_array=( [m]=manifest= ) local manifest @@ -129,6 +139,8 @@ ynh_app_package_version () { # example: sudo YNH_FORCE_UPGRADE=1 yunohost app upgrade MyApp # # usage: ynh_check_app_version_changed +# +# Requires YunoHost version 3.?.? or higher. ynh_check_app_version_changed () { local force_upgrade=${YNH_FORCE_UPGRADE:-0} local package_check=${PACKAGE_CHECK_EXEC:-0} diff --git a/data/helpers.d/user b/data/helpers.d/user index d716bf03b..f19739993 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -6,6 +6,8 @@ # # usage: ynh_user_exists --username=username # | arg: -u, --username - the username to check +# +# Requires YunoHost version 2.2.4 or higher. ynh_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u @@ -25,6 +27,8 @@ ynh_user_exists() { # | arg: -u, --username - the username to retrieve info from # | arg: -k, --key - the key to retrieve # | ret: string - the key's value +# +# Requires YunoHost version 2.2.4 or higher. ynh_user_get_info() { # Declare an array to define the options of this helper. local legacy_args=uk @@ -43,6 +47,8 @@ ynh_user_get_info() { # # usage: ynh_user_list # | ret: string - one username per line +# +# Requires YunoHost version 2.4.0 or higher. ynh_user_list() { sudo yunohost user list --output-as plain --quiet \ | awk '/^##username$/{getline; print}' @@ -52,6 +58,8 @@ ynh_user_list() { # # usage: ynh_system_user_exists --username=username # | arg: -u, --username - the username to check +# +# Requires YunoHost version 2.2.4 or higher. ynh_system_user_exists() { # Declare an array to define the options of this helper. local legacy_args=u @@ -76,6 +84,8 @@ ynh_system_user_exists() { # | arg: -h, --home_dir - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home # | arg: -s, --use_shell - Create a user using the default login shell if present. # If this argument is omitted, the user will be created with /usr/sbin/nologin shell +# +# Requires YunoHost version 2.6.4 or higher. ynh_system_user_create () { # Declare an array to define the options of this helper. local legacy_args=uhs @@ -108,6 +118,8 @@ ynh_system_user_create () { # # usage: ynh_system_user_delete --username=user_name # | arg: -u, --username - Name of the system user that will be create +# +# Requires YunoHost version 2.6.4 or higher. ynh_system_user_delete () { # Declare an array to define the options of this helper. local legacy_args=u diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 5ba2946a2..5f5e61015 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -6,6 +6,8 @@ # # usage: ynh_get_plain_key key [subkey [subsubkey ...]] # | ret: string - the key's value +# +# Requires YunoHost version 2.2.4 or higher. ynh_get_plain_key() { local prefix="#" local founded=0 @@ -36,6 +38,7 @@ ynh_get_plain_key() { # } # ynh_abort_if_errors # +# Requires YunoHost version 2.7.2 or higher. ynh_restore_upgradebackup () { echo "Upgrade failed." >&2 local app_bck=${app//_/-} # Replace all '_' by '-' @@ -67,6 +70,7 @@ ynh_restore_upgradebackup () { # } # ynh_abort_if_errors # +# Requires YunoHost version 2.7.2 or higher. ynh_backup_before_upgrade () { if [ ! -e "/etc/yunohost/apps/$app/scripts/backup" ] then @@ -150,6 +154,8 @@ ynh_backup_before_upgrade () { # usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] # | arg: -d, --dest_dir - Directory where to setup sources # | arg: -s, --source_id - Name of the app, if the package contains more than one app +# +# Requires YunoHost version 2.6.4 or higher. ynh_setup_source () { # Declare an array to define the options of this helper. local legacy_args=ds @@ -255,6 +261,8 @@ ynh_setup_source () { # | arg: key1=value1 - (Optionnal) POST key and corresponding value # | arg: key2=value2 - (Optionnal) Another POST key and corresponding value # | arg: ... - (Optionnal) More POST keys and values +# +# Requires YunoHost version 2.6.4 or higher. ynh_local_curl () { # Define url of page to curl local local_page=$(ynh_normalize_url_path $1) From 2703d5aa66d020460a187985be146f2484fb25e9 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 26 Feb 2019 00:07:53 +0100 Subject: [PATCH 297/721] Use apps.json as default list --- src/yunohost/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a011b1546..11d90dd09 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -415,10 +415,10 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False, # Enable UPnP silently and reload firewall firewall_upnp('enable', no_refresh=True) - # Setup the default official app list with cron job + # Setup the default apps list with cron job try: app_fetchlist(name="yunohost", - url="https://app.yunohost.org/official.json") + url="https://app.yunohost.org/apps.json") except Exception as e: logger.warning(str(e)) From 990eeb76e1cf4092695ce92490a46925a797884a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 26 Feb 2019 00:40:16 +0100 Subject: [PATCH 298/721] Missing space breaks bash parsing of the whole file --- data/helpers.d/nodejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 61a1414ef..098ed4410 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -99,7 +99,7 @@ ynh_install_nodejs () { # Install the requested version of nodejs uname=$(uname -m) - if [[ $uname =~ aarch64 || $uname =~ arm64]] + if [[ $uname =~ aarch64 || $uname =~ arm64 ]] then n $nodejs_version --arch=arm64 else From ac6f24671cce306251d12c9913e49a622be83981 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 26 Feb 2019 00:59:31 +0100 Subject: [PATCH 299/721] In case no file in dir, still an empty string will be read, which matches [[:digit:]]* (but won't with +) --- data/helpers.d/package | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/package b/data/helpers.d/package index 3924fc14e..000b0ee74 100644 --- a/data/helpers.d/package +++ b/data/helpers.d/package @@ -25,7 +25,7 @@ ynh_wait_dpkg_free() { while read dpkg_file <&9 do # Check if the name of this file contains only numbers. - if echo "$dpkg_file" | grep -Pq "^[[:digit:]]*$" + if echo "$dpkg_file" | grep -Pq "^[[:digit:]]+$" then # If so, that a remaining of dpkg. ynh_print_err "E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem." From 5d9a62192f02cd27ac3dc0ed93fbd8edcfaced63 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 26 Feb 2019 01:32:52 +0100 Subject: [PATCH 300/721] Migrate to apps.json --- .../0009_migrate_to_apps_json.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/yunohost/data_migrations/0009_migrate_to_apps_json.py diff --git a/src/yunohost/data_migrations/0009_migrate_to_apps_json.py b/src/yunohost/data_migrations/0009_migrate_to_apps_json.py new file mode 100644 index 000000000..364497b46 --- /dev/null +++ b/src/yunohost/data_migrations/0009_migrate_to_apps_json.py @@ -0,0 +1,27 @@ +from moulinette.utils.log import getActionLogger +from yunohost.app import app_fetchlist, app_removelist +from yunohost.tools import Migration + +logger = getActionLogger('yunohost.migration') + +class MyMigration(Migration): + + "Migrate from official.json to apps.json" + + def migrate(self): + + # Remove official.json list + app_removelist(name="yunohost") + + # Replace by apps.json list + app_fetchlist(name="yunohost", + url="https://app.yunohost.org/apps.json") + + def backward(self): + + # Remove apps.json list + app_removelist(name="yunohost") + + # Replace by official.json list + app_fetchlist(name="yunohost", + url="https://app.yunohost.org/official.json") From b3edc2a63ccf23c95d7e58f9b3b17635372686b9 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 26 Feb 2019 01:59:03 +0100 Subject: [PATCH 301/721] Fix ynh_add_fail2ban_config legacy mode Merged as a micro decision Fix https://github.com/YunoHost/issues/issues/1313 --- data/helpers.d/backend | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index e710da9c7..1532601a8 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -352,6 +352,7 @@ ynh_remove_fpm_config () { # ynh_add_fail2ban_config () { # Declare an array to define the options of this helper. + local legacy_args=lrmptv declare -Ar args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=) local logpath local failregex From b49eeddcf94ee4d733d10a9db657281d36648e8a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 26 Feb 2019 02:20:41 +0100 Subject: [PATCH 302/721] Drunk commits were drunk --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e84187d6b..a622c0f24 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -593,12 +593,12 @@ def app_upgrade(auth, app=[], url=None, file=None): apps = [app] # Remove possible duplicates - apps = [app for i,app in enumerate(apps) if apps not in L[:i]] + apps = [app for i,app in enumerate(apps) if apps not in apps[:i]] if len(apps) == 0: raise YunohostError('app_no_upgrade') if len(apps) > 1: - logger.info(m18n.n("app_upgrade_several_apps", apps=", ".join(app))) + logger.info(m18n.n("app_upgrade_several_apps", apps=", ".join(apps))) for app_instance_name in apps: logger.info(m18n.n('app_upgrade_app_name', app=app_instance_name)) From 366d8231eb7d29708e4dea7a243c210acaa11c3f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 26 Feb 2019 02:29:18 +0100 Subject: [PATCH 303/721] Makes no sense to not show those messages only if that's a user-specified list ... --- src/yunohost/app.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a622c0f24..a91ba61eb 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -583,12 +583,11 @@ def app_upgrade(auth, app=[], url=None, file=None): not_upgraded_apps = [] apps = app - user_specified_list = True # If no app is specified, upgrade all apps if not apps: + # FIXME : not sure what's supposed to happen if there is a url and a file but no apps... if not url and not file: apps = [app["id"] for app in app_list(installed=True)["apps"]] - user_specified_list = False elif not isinstance(app, list): apps = [app] @@ -618,8 +617,7 @@ def app_upgrade(auth, app=[], url=None, file=None): elif app_dict["upgradable"] == "yes": manifest, extracted_app_folder = _fetch_app_from_git(app_instance_name) else: - if user_specified_list: - logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) + logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) continue # Check requirements From 15b3733e2241cdc8322e19399827c2f65772399f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 26 Feb 2019 02:34:02 +0100 Subject: [PATCH 304/721] Don't raise an exception in the middle of the loop, that check can be done before --- src/yunohost/app.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index a91ba61eb..be0bb5a55 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -594,6 +594,10 @@ def app_upgrade(auth, app=[], url=None, file=None): # Remove possible duplicates apps = [app for i,app in enumerate(apps) if apps not in apps[:i]] + # Abort if any of those app is in fact not installed.. + for app in [app for app in apps if not _is_installed(app)]: + raise YunohostError('app_not_installed', app=app) + if len(apps) == 0: raise YunohostError('app_no_upgrade') if len(apps) > 1: @@ -601,9 +605,6 @@ def app_upgrade(auth, app=[], url=None, file=None): for app_instance_name in apps: logger.info(m18n.n('app_upgrade_app_name', app=app_instance_name)) - installed = _is_installed(app_instance_name) - if not installed: - raise YunohostError('app_not_installed', app=app_instance_name) app_dict = app_info(app_instance_name, raw=True) From f51101152a04fd6672198d1712c0886d27a80cc7 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 26 Feb 2019 15:37:50 +0100 Subject: [PATCH 305/721] Missing } --- data/helpers.d/system | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/system b/data/helpers.d/system index 604cf7bc6..cc1dbbcc5 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -148,6 +148,7 @@ ynh_clean_check_starting () { # Stop the execution of tail. kill -s 15 $pid_tail 2>&1 ynh_secure_remove "$templog" 2>&1 +} # Read the value of a key in a ynh manifest file # From e6f1845448c86c32a379353c8c0a70797135a52b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Mar 2019 01:40:52 +0100 Subject: [PATCH 306/721] Add -f to rm --- src/yunohost/dyndns.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index 36509b0e4..ddc285e8d 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -157,12 +157,12 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom try: r = requests.post('https://%s/key/%s?key_algo=hmac-sha512' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}, timeout=30) except requests.ConnectionError: - os.system("rm %s" % private_file) - os.system("rm %s" % key_file) + os.system("rm -f %s" % private_file) + os.system("rm -f %s" % key_file) raise YunohostError('no_internet_connection') if r.status_code != 201: - os.system("rm %s" % private_file) - os.system("rm %s" % key_file) + os.system("rm -f %s" % private_file) + os.system("rm -f %s" % key_file) try: error = json.loads(r.text)['error'] except: From 4a8563ed9e86d4aaedc8b189373056b505a250b6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 Mar 2019 01:43:16 +0100 Subject: [PATCH 307/721] Clarify the if case for 'get' --- data/helpers.d/setting | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index 18a57780a..ccd2ac0a6 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -75,9 +75,11 @@ else: if action == "delete": if key in settings: del settings[key] - else: + elif action == "get": if key in ['redirected_urls', 'redirected_regex']: value = yaml.load(value) + else: + raise ValueError("action should either be get, set or delete") settings[key] = value with open(setting_file, "w") as f: yaml.safe_dump(settings, f, default_flow_style=False) From 078b9b90bc22c56fc78255ae5fc72bea9e0e5af5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 3 Mar 2019 00:50:36 +0100 Subject: [PATCH 308/721] Drunk commits were made >.> --- data/helpers.d/setting | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index ccd2ac0a6..fb833281a 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -75,12 +75,12 @@ else: if action == "delete": if key in settings: del settings[key] - elif action == "get": + elif action == "set": if key in ['redirected_urls', 'redirected_regex']: value = yaml.load(value) + settings[key] = value else: raise ValueError("action should either be get, set or delete") - settings[key] = value with open(setting_file, "w") as f: yaml.safe_dump(settings, f, default_flow_style=False) EOF From 12c61eeb6507faddf902a0bdc9a7c13fbba2fdbe Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 3 Mar 2019 16:38:54 +0100 Subject: [PATCH 309/721] Use printers --- data/helpers.d/system | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index cc1dbbcc5..f319f3f7b 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -106,10 +106,10 @@ ynh_systemd_action() { fi fi - echo "${action^} the service $service_name" >&2 + ynh_print_info --message="${action^} the service $service_name" systemctl $action $service_name \ || ( journalctl --no-pager --lines=$length -u $service_name >&2 \ - ; test -e "$log_path" && echo "--" && tail --lines=$length "$log_path" >&2 \ + ; test -e "$log_path" && echo "--" >&2 && tail --lines=$length "$log_path" >&2 \ ; false ) # Start the timeout and try to find line_match @@ -121,21 +121,20 @@ ynh_systemd_action() { # Read the log until the sentence is found, that means the app finished to start. Or run until the timeout if grep --quiet "$line_match" "$templog" then - echo "The service $service_name has correctly started." >&2 + ynh_print_info --message="The service $service_name has correctly started." break fi echo -n "." >&2 sleep 1 done + echo "" >&2 if [ $i -eq $timeout ] then - echo "The service $service_name didn't fully started before the timeout." >&2 - echo "Please find here an extract of the end of the log of the service $service_name:" + ynh_print_warn --message="The service $service_name didn't fully started before the timeout." + ynh_print_warn --message="Please find here an extract of the end of the log of the service $service_name:" journalctl --no-pager --lines=$length -u $service_name >&2 - test -e "$log_path" && echo "--" && tail --lines=$length "$log_path" >&2 + test -e "$log_path" && echo "--" >&2 && tail --lines=$length "$log_path" >&2 fi - - echo "" ynh_clean_check_starting fi } From 58af343a84d2319b2a70b7e06133953c99d1af60 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 3 Mar 2019 17:25:07 +0100 Subject: [PATCH 310/721] Add warning about exec_* printers --- data/helpers.d/print | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/helpers.d/print b/data/helpers.d/print index 7f37021ae..b46192d87 100644 --- a/data/helpers.d/print +++ b/data/helpers.d/print @@ -89,6 +89,7 @@ ynh_print_err () { # usage: ynh_exec_err command to execute # usage: ynh_exec_err "command to execute | following command" # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. +# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # | arg: command - command to execute ynh_exec_err () { @@ -100,6 +101,7 @@ ynh_exec_err () { # usage: ynh_exec_warn command to execute # usage: ynh_exec_warn "command to execute | following command" # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. +# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # | arg: command - command to execute ynh_exec_warn () { @@ -111,6 +113,7 @@ ynh_exec_warn () { # usage: ynh_exec_warn_less command to execute # usage: ynh_exec_warn_less "command to execute | following command" # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. +# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # | arg: command - command to execute ynh_exec_warn_less () { @@ -122,6 +125,7 @@ ynh_exec_warn_less () { # usage: ynh_exec_quiet command to execute # usage: ynh_exec_quiet "command to execute | following command" # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. +# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # | arg: command - command to execute ynh_exec_quiet () { @@ -133,6 +137,7 @@ ynh_exec_quiet () { # usage: ynh_exec_fully_quiet command to execute # usage: ynh_exec_fully_quiet "command to execute | following command" # In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe. +# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed. # # | arg: command - command to execute ynh_exec_fully_quiet () { From 337a065b4a074a0a72b38d195875e1956136a804 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 3 Mar 2019 18:57:41 +0100 Subject: [PATCH 311/721] Print progression only after 3s --- data/helpers.d/system | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index f319f3f7b..7061e261f 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -124,10 +124,17 @@ ynh_systemd_action() { ynh_print_info --message="The service $service_name has correctly started." break fi - echo -n "." >&2 + if [ $i -eq 3 ]; then + echo -n "Please wait, the service $service_name is ${action}ing" >&2 + fi + if [ $i -ge 3 ]; then + echo -n "." >&2 + fi sleep 1 done - echo "" >&2 + if [ $i -ge 3 ]; then + echo "" >&2 + fi if [ $i -eq $timeout ] then ynh_print_warn --message="The service $service_name didn't fully started before the timeout." From 08e51f2680a82bf2fbf5a5de826e49cda86422ff Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 3 Mar 2019 18:58:38 +0100 Subject: [PATCH 312/721] Do not show errors about log not existing yet. --- data/helpers.d/system | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index 7061e261f..bfc20cb33 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -100,7 +100,7 @@ ynh_systemd_action() { local pid_tail=$! else # Read the specified log file - tail -F -n0 "$log_path" > "$templog" & + tail -F -n0 "$log_path" > "$templog" 2>&1 & # Get the PID of the tail command local pid_tail=$! fi From e81e232fb216fb2f88cb381b42166bacea245b6e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Mar 2019 03:09:23 +0100 Subject: [PATCH 313/721] Add a check that some required services are up before running install and upgrade --- locales/en.json | 1 + src/yunohost/app.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 1157e0a54..f10542951 100644 --- a/locales/en.json +++ b/locales/en.json @@ -4,6 +4,7 @@ "admin_password": "Administration password", "admin_password_change_failed": "Unable to change password", "admin_password_changed": "The administration password has been changed", + "app_action_cannot_be_ran_because_required_services_down": "This app requires some services which are currently down. Before continuing, you should try to restart the following services (and possibly investigate why they are down) : {services}", "app_already_installed": "{app:s} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The url cannot be changed just by this function. Look into `app changeurl` if it's available.", "app_already_up_to_date": "{app:s} is already up to date", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index be0bb5a55..4997727e3 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -42,7 +42,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from moulinette.utils.filesystem import read_json -from yunohost.service import service_log, _run_service_command +from yunohost.service import service_log, service_status, _run_service_command from yunohost.utils import packages from yunohost.log import is_unit_operation, OperationLogger @@ -623,6 +623,7 @@ def app_upgrade(auth, app=[], url=None, file=None): # Check requirements _check_manifest_requirements(manifest, app_instance_name=app_instance_name) + _check_services_status_for_app(manifest.get("services", [])) app_setting_path = APPS_SETTING_PATH + '/' + app_instance_name @@ -778,6 +779,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on # Check requirements _check_manifest_requirements(manifest, app_id) + _check_services_status_for_app(manifest.get("services", [])) # Check if app can be forked instance_number = _installed_instance_number(app_id, last=True) + 1 @@ -2574,6 +2576,31 @@ def unstable_apps(): return output +def _check_services_status_for_app(services): + + logger.debug("Checking that required services are up and running...") + + # Some apps use php-fpm or php5-fpm which is now php7.0-fpm + def replace_alias(service): + if service in ["php-fpm", "php5-fpm"]: + return "php7.0-fpm" + else: + return service + services = [replace_alias(s) for s in services] + + # We only check those, mostly to ignore "custom" services + # (added by apps) and because those are the most popular + # services + service_filter = ["nginx", "php7.0-fpm", "mysql", "postfix"] + services = [s for s in services if s in service_filter] + + # List services currently down and raise an exception if any are found + faulty_services = [s for s, infos in service_status(services).items() if infos["active"] != "active"] + if faulty_services: + raise YunohostError('app_action_cannot_be_ran_because_required_services_down', + services=', '.join(faulty_services)) + + def _patch_php5(app_folder): files_to_patch = [] From b8c5e9f1b6b0333c652ae89541baa80204ac054f Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 4 Mar 2019 02:59:14 +0100 Subject: [PATCH 314/721] [enh] allow 'display_text' ~fake~ argument in manifest.json --- src/yunohost/app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index be0bb5a55..8561e2667 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2202,6 +2202,11 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None): if arg_type == 'boolean': arg_default = 1 if arg_default else 0 + # do not print for webadmin + if arg_type == 'display_text' and msettings.get('interface') != 'api': + print(arg["text"]) + continue + # Attempt to retrieve argument value if arg_name in args: arg_value = args[arg_name] From f37a6b43e1ab4e163877b6afdd3226009cfd70a2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Mar 2019 17:09:31 +0100 Subject: [PATCH 315/721] More general exception to handle other cases (e.g. SSL errors, ...) --- src/yunohost/dyndns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/dyndns.py b/src/yunohost/dyndns.py index ddc285e8d..2dadcef52 100644 --- a/src/yunohost/dyndns.py +++ b/src/yunohost/dyndns.py @@ -156,10 +156,10 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom # Send subscription try: r = requests.post('https://%s/key/%s?key_algo=hmac-sha512' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}, timeout=30) - except requests.ConnectionError: + except Exception as e: os.system("rm -f %s" % private_file) os.system("rm -f %s" % key_file) - raise YunohostError('no_internet_connection') + raise YunohostError('dyndns_registration_failed', error=str(e)) if r.status_code != 201: os.system("rm -f %s" % private_file) os.system("rm -f %s" % key_file) From 877fe506850b388807cfebf0e9d7561776ac2637 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Mar 2019 17:21:57 +0100 Subject: [PATCH 316/721] Use a finally clause to avoid duplicated code --- src/yunohost/hook.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index d9cad9c7a..961caf606 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -403,13 +403,11 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, else: returnjson = {} except Exception as e: + raise YunohostError('hook_json_return_error', path=path, msg=str(e)) + finally: + stdreturndir = os.path.split(stdreturn)[0] os.remove(stdreturn) os.rmdir(stdreturndir) - raise YunohostError('hook_json_return_error', path=path, msg=str(e)) - - stdreturndir = os.path.split(stdreturn)[0] - os.remove(stdreturn) - os.rmdir(stdreturndir) return returncode, returnjson From c74bff31d12e08acc80f54f628e09ce8c16f7adc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Mar 2019 17:44:20 +0100 Subject: [PATCH 317/721] Fix listing of succeed/failed hook names --- src/yunohost/service.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 151a877b9..61274aaac 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -493,11 +493,14 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False, pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call) - # Update the services name - names = {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in pre_result.items()}.keys() + # Keep only the hook names with at least one success + names = [hook for hook, infos in pre_result.items() + if any(result["state"] == "succeed" for result in infos.values())] + # FIXME : what do in case of partial success/failure ... if not names: - ret_failed = {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in pre_result.items()} + ret_failed = [hook for hook, infos in pre_result.items() + if any(result["state"] == "failed" for result in infos.values())] raise YunohostError('service_regenconf_failed', services=', '.join(ret_failed)) From 16245f53d9591fcfeca70e5c54604ba3d26c2185 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Mar 2019 18:33:09 +0100 Subject: [PATCH 318/721] Fix listing of succeed/failed for backup and restore --- src/yunohost/backup.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index d33f75e3a..872896ecb 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -593,10 +593,14 @@ class BackupManager(): env=env_dict, chdir=self.work_dir) - ret_succeed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "succeed"] for n, v in ret.items()}.items() if val} - ret_failed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in ret.items()}.items() if val} + ret_succeed = {hook: {path:result["state"] for path, result in infos.items()} + for hook, infos in ret.items() + if any(result["state"] == "succeed" for result in infos.values())} + ret_failed = {hook: {path:result["state"] for path, result in infos.items.items()} + for hook, infos in ret.items() + if any(result["state"] == "failed" for result in infos.values())} - if ret_succeed != []: + if ret_succeed.keys() != []: self.system_return = ret_succeed # Add files from targets (which they put in the CSV) to the list of @@ -1180,14 +1184,16 @@ class RestoreManager(): env=env_dict, chdir=self.work_dir) - ret_succeed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "succeed"] for n, v in ret.items()}.items() if val} - ret_failed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in ret.items()}.items() if val} + ret_succeed = [hook for hook, infos in ret.items() + if any(result["state"] == "succeed" for result in infos.values())] + ret_failed = [hook for hook, infos in ret.items() + if any(result["state"] == "failed" for result in infos.values())] - for part in ret_succeed.keys(): + for part in ret_succeed: self.targets.set_result("system", part, "Success") error_part = [] - for part in ret_failed.keys(): + for part in ret_failed: logger.error(m18n.n('restore_system_part_failed', part=part)) self.targets.set_result("system", part, "Error") error_part.append(part) From 88b7d77610d6493d62a490fa3ff758a992e7e270 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Mar 2019 18:58:04 +0100 Subject: [PATCH 319/721] Display raw json content in case of exception --- locales/en.json | 2 +- src/yunohost/hook.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/locales/en.json b/locales/en.json index a6d95ada1..ca1d9c8a1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -211,7 +211,7 @@ "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", - "hook_json_return_error": "Failed to read return from hook {path:s}. Error: {msg:s}", + "hook_json_return_error": "Failed to read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}", "hook_list_by_invalid": "Invalid property to list hook by", "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation complete", diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 961caf606..c4605b6e8 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -395,15 +395,17 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, elif raise_on_error and returncode != 0: raise YunohostError('hook_exec_failed', path=path) + raw_content = None try: - - with open(stdreturn, 'r') as f: - if f.read() != '': - returnjson = read_json(stdreturn) - else: - returnjson = {} + with open(stdreturn, 'r') as f: + raw_content = f.read() + if raw_content != '': + returnjson = read_json(stdreturn) + else: + returnjson = {} except Exception as e: - raise YunohostError('hook_json_return_error', path=path, msg=str(e)) + raise YunohostError('hook_json_return_error', path=path, msg=str(e), + raw_content=raw_content) finally: stdreturndir = os.path.split(stdreturn)[0] os.remove(stdreturn) From 5516e96d076eba0fc98f714b73e365693ddfc7a2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Mar 2019 19:05:43 +0100 Subject: [PATCH 320/721] Simpler fetching of failed/succeeded hooks in CustomBackupMethod --- src/yunohost/backup.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 872896ecb..867de3ea9 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1941,7 +1941,8 @@ class CustomBackupMethod(BackupMethod): ret = hook_callback('backup_method', [self.method], args=self._get_args('need_mount')) - ret_succeed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "succeed"] for n, v in ret.items()}.items() if val} + ret_succeed = [hook for hook, infos in ret.items() + if any(result["state"] == "succeed" for result in infos.values())] self._need_mount = True if ret_succeed else False return self._need_mount @@ -1955,7 +1956,9 @@ class CustomBackupMethod(BackupMethod): ret = hook_callback('backup_method', [self.method], args=self._get_args('backup')) - ret_failed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in ret.items()}.items() if val} + + ret_failed = [hook for hook, infos in ret.items() + if any(result["state"] == "failed" for result in infos.values())] if ret_failed: raise YunohostError('backup_custom_backup_error') @@ -1969,7 +1972,9 @@ class CustomBackupMethod(BackupMethod): super(CustomBackupMethod, self).mount(restore_manager) ret = hook_callback('backup_method', [self.method], args=self._get_args('mount')) - ret_failed = {k: val for k, val in {n: [p for p, c in v.items() if c['state'] == "failed"] for n, v in ret.items()}.items() if val} + + ret_failed = [hook for hook, infos in ret.items() + if any(result["state"] == "failed" for result in infos.values())] if ret_failed: raise YunohostError('backup_custom_mount_error') From e3dfd63481fd72109ff2bf8bac562b123b95ad90 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 4 Mar 2019 22:54:02 +0100 Subject: [PATCH 321/721] Replace hard-corded value with constant defined above --- data/helpers.d/psql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 324cfee83..705aeeb9a 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -244,7 +244,7 @@ ynh_psql_remove_db() { # # usage: ynh_psql_test_if_first_run ynh_psql_test_if_first_run() { - if [ -f /etc/yunohost/psql ]; then + if [ -f "$PSQL_ROOT_PWD_FILE" ]; then echo "PostgreSQL is already installed, no need to create master password" else local pgsql="$(ynh_string_random)" From 59714fdb1b264d72786fbbb15d7ca43406ddb2be Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 00:11:06 +0100 Subject: [PATCH 322/721] If url is None, we want to feed None and not [None] --- src/yunohost/data_migrations/0009_setup_group_permission.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 47d0d53a2..73be422b3 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -73,10 +73,8 @@ def migrate_app_permission(auth, app=None): path = app_setting(app, 'path') domain = app_setting(app, 'domain') - url = None - if domain and path: - url = domain + path - permission_add(auth, app, permission='main', urls=[url], default_allow=True, sync_perm=False) + urls = [domain + path] if domain and path else None + permission_add(auth, app, permission='main', urls=urls, default_allow=True, sync_perm=False) if permission: allowed_group = permission.split(',') user_permission_add(auth, [app], permission='main', group=allowed_group, sync_perm=False) From 47e6bf95dd6e3fe62a1b1bf77f62685b6935c232 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 00:31:01 +0100 Subject: [PATCH 323/721] Improve exception handling ... the 'real' traceback is shown by the parent scope, but was lost in the current code because we triggered another exception --- locales/en.json | 5 ++--- src/yunohost/data_migrations/0009_setup_group_permission.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index c58692f01..62c6603c1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -339,11 +339,10 @@ "migration_0009_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}", "migration_0009_create_group": "Create group for each user.", "migration_0009_done": "Migration sucess. You are now able to use groups of user.", - "migration_0009_failed": "Migration failed.", "migration_0009_LDAP_update_failed": "LDAP update failed. Error: {error:s}", "migration_0009_migrate_permission": "Migrate permission from apps settings to LDAP", - "migration_0009_migration_failed_try_rollback": "Migration failed. Try to restore the system. Error: {error:s}", - "migration_0009_rollback_success": "System restored.", + "migration_0009_migration_failed_trying_to_rollback": "Migration failed ... trying to rollback the system.", + "migration_0009_rollback_success": "Rollback succeeded.", "migration_0009_update_LDAP_database": "Update LDAP database for groups and permission support", "migration_0009_update_LDAP_schema": "Update LDAP schema", "migrations_backward": "Migrating backward.", diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 73be422b3..9176d2969 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -124,7 +124,7 @@ class MyMigration(Migration): permission_sync_to_user(auth) except Exception as e: - logger.warn(m18n.n("migration_0009_migration_failed_try_rollback", error=e)) + logger.warn(m18n.n("migration_0009_migration_failed_trying_to_rollback")) os.system("systemctl stop slapd") os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) @@ -133,7 +133,7 @@ class MyMigration(Migration): os.system("systemctl start slapd") os.system("rm -r " + backup_folder) logger.info(m18n.n("migration_0009_rollback_success")) - raise YunohostError("migration_0009_failed") + raise os.system("rm -r " + backup_folder) From f845187230db6694783f74ce87186370f03f4367 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 00:32:14 +0100 Subject: [PATCH 324/721] Let's be paranoid and include all that stuff in the try/except as well --- .../0009_setup_group_permission.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index 9176d2969..a1f4ca6ee 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -104,18 +104,18 @@ class MyMigration(Migration): except Exception as e: raise YunohostError("migration_0009_can_not_backup_before_migration", error=e) - # Update LDAP schema restart slapd - logger.info(m18n.n("migration_0009_update_LDAP_schema")) - service_regen_conf(names=['slapd'], force=True) - - # Do the authentication to LDAP after LDAP as been updated - AUTH_IDENTIFIER = ('ldap', 'as-root') - AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', - 'base_dn': 'dc=yunohost,dc=org', - 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} - auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) - try: + # Update LDAP schema restart slapd + logger.info(m18n.n("migration_0009_update_LDAP_schema")) + service_regen_conf(names=['slapd'], force=True) + + # Do the authentication to LDAP after LDAP as been updated + AUTH_IDENTIFIER = ('ldap', 'as-root') + AUTH_PARAMETERS = {'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', + 'base_dn': 'dc=yunohost,dc=org', + 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} + auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) + #Update LDAP database migrate_LDAP_db(auth) @@ -134,7 +134,7 @@ class MyMigration(Migration): os.system("rm -r " + backup_folder) logger.info(m18n.n("migration_0009_rollback_success")) raise + else: + os.system("rm -r " + backup_folder) - os.system("rm -r " + backup_folder) - - logger.info(m18n.n("migration_0009_done")) + logger.info(m18n.n("migration_0009_done")) From def005d2da29763fc7d45d3398e0f82a896641d6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 00:42:20 +0100 Subject: [PATCH 325/721] Fix weird message --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 1157e0a54..f7a65a883 100644 --- a/locales/en.json +++ b/locales/en.json @@ -218,7 +218,7 @@ "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "hook_exec_failed": "Script execution failed: {path:s}", - "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", + "hook_exec_not_terminated": "Script execution did not finish properly: {path:s}", "hook_list_by_invalid": "Invalid property to list hook by", "hook_name_unknown": "Unknown hook name '{name:s}'", "installation_complete": "Installation complete", From d49b098e67b5e017eb0d7965233a91c71f845a96 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 00:44:16 +0100 Subject: [PATCH 326/721] Specify group name in group-related messages --- locales/en.json | 12 ++++++------ src/yunohost/user.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/locales/en.json b/locales/en.json index 62c6603c1..d643f07fa 100644 --- a/locales/en.json +++ b/locales/en.json @@ -214,15 +214,15 @@ "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled for app '{app:s}'", "group_already_disallowed": "Group '{group:s}' already has permissions '{permission:s}' disabled for app '{app:s}'", "group_name_already_exist": "Group {name:s} already exist", - "group_created": "Group creation success", - "group_creation_failed": "Group creation failed", - "group_deleted": "Group deleted", - "group_deletion_failed": "Group deletion failed", + "group_created": "Group '{group}' succesfully created", + "group_creation_failed": "Group creation failed for group '{group}'", + "group_deleted": "Group '{group}' deleted", + "group_deletion_failed": "Group '{group} 'deletion failed", "group_deletion_not_allowed": "You are not allowed to remove the main group of the user {user:s}", "group_info_failed": "Group info failed", "group_unknown": "Group {group:s} unknown", - "group_updated": "Group updated", - "group_update_failed": "Group update failed", + "group_updated": "Group '{group}' updated", + "group_update_failed": "Group update failed for group '{group}'", "hook_exec_failed": "Script execution failed: {path:s}", "hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}", "hook_list_by_invalid": "Invalid property to list hook by", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 442f499d3..e1f706577 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -575,12 +575,12 @@ def user_group_add(operation_logger, auth, groupname,gid=None, sync_perm=True): } if auth.add('cn=%s,ou=groups' % groupname, attr_dict): - logger.success(m18n.n('group_created')) + logger.success(m18n.n('group_created', group=groupname)) if sync_perm: permission_sync_to_user(auth) return {'name': groupname} - raise YunohostError('group_creation_failed') + raise YunohostError('group_creation_failed', group=groupname) @is_unit_operation([('groupname', 'user')]) @@ -599,9 +599,9 @@ def user_group_delete(operation_logger, auth, groupname, force=False, sync_perm= operation_logger.start() if not auth.remove('cn=%s,ou=groups' % groupname): - raise YunohostError('group_deletion_failed') + raise YunohostError('group_deletion_failed', group=groupname) - logger.success(m18n.n('group_deleted')) + logger.success(m18n.n('group_deleted', group=groupname)) if sync_perm: permission_sync_to_user(auth) @@ -677,9 +677,9 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u if new_group_list['member'] != set(group['member']): if not auth.update('cn=%s,ou=groups' % groupname, new_group_list): - raise YunohostError('group_update_failed') + raise YunohostError('group_update_failed', group=groupname) - logger.success(m18n.n('group_updated')) + logger.success(m18n.n('group_updated', group=groupname)) if sync_perm: permission_sync_to_user(auth) return user_group_info(auth, groupname) From 81add41fddb40a126d4cf439486eae175c73d5a6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 01:33:50 +0100 Subject: [PATCH 327/721] Misc wording / formatting --- locales/en.json | 2 +- src/yunohost/permission.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/locales/en.json b/locales/en.json index d643f07fa..6f9d28a40 100644 --- a/locales/en.json +++ b/locales/en.json @@ -416,7 +416,7 @@ "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", "permission_name_not_valid": "Permission name '{permission:s}' not valid", "permission_update_failed": "Permission update failed", - "permission_generated": "Permission updated", + "permission_generated": "The permission database has been updated.", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index 61615cbb5..aabf7f7a0 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -445,11 +445,14 @@ def permission_remove(operation_logger, auth, app, permission, force=False, sync def permission_sync_to_user(auth, force=False): """ - Sychronise the inheritPermission attribut in the permission object from the user<->group link and the group<->permission link + Sychronise the inheritPermission attribut in the permission object from the + user<->group link and the group<->permission link Keyword argument: - force -- Force to recreate all attributes. Used generally with the backup which wich use "slapadd" which don't use the memberOf overlay. - Note that by removing all value and adding a new time, we force the overlay to update all attributes + force -- Force to recreate all attributes. Used generally with the + backup which uses "slapadd" which doesnt' use the memberOf overlay. + Note that by removing all value and adding a new time, we force the + overlay to update all attributes """ # Note that a LDAP operation with the same value that is in LDAP crash SLAP. # So we need to check before each ldap operation that we really change something in LDAP @@ -505,6 +508,6 @@ def permission_sync_to_user(auth, force=False): app_ssowatconf(auth) - # Reload unscd because if not the group is not updated in the system from LDAP + # Reload unscd, otherwise the group ain't propagated to the LDAP database os.system('nscd --invalidate=passwd') os.system('nscd --invalidate=group') From b7aa7f6fbb8f8f02e5cfb51f977fcaa9e7daa9ed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 01:43:19 +0100 Subject: [PATCH 328/721] Cover small edge case where domain key doesnt exist --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index badebf83f..5e291bcec 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -911,7 +911,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on # Add path in permission if it's defined in the app install script app_settings = _get_app_settings(app_instance_name) - domain = app_settings['domain'] + domain = app_settings.get('domain', None) path = app_settings.get('path', None) if domain and path: permission_update(auth, app_instance_name, permission="main", add_url=[domain+path], sync_perm=False) From c3e92f245fbde586d39ea22c2328336589b26e9d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 01:43:33 +0100 Subject: [PATCH 329/721] Use _get_migration_by_name to fetch migration module --- src/yunohost/backup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 841b81db7..6c37d7f00 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -903,12 +903,12 @@ class RestoreManager(): '(&(objectclass=groupOfNamesYnh)(cn=all_users))', ['cn']) if not result: - from importlib import import_module - migration_0009 = import_module('yunohost.data_migrations.0009_setup_group_permission') + from yunohost.tools import _get_migration_by_name + setup_group_permission = _get_migration_by_name("setup_group_permission") # Update LDAP schema restart slapd logger.info(m18n.n("migration_0009_update_LDAP_schema")) service_regen_conf(names=['slapd'], force=True) - migration_0009.migrate_LDAP_db(auth) + setup_group_permission.migrate_LDAP_db(auth) def clean(self, auth): """ @@ -1305,9 +1305,9 @@ class RestoreManager(): if os.path.isfile(app_restore_script_in_archive): os.system("slapadd -l '%s/permission.ldif'" % app_settings_in_archive) else: - from importlib import import_module - migration_0009 = import_module('yunohost.data_migrations.0009_setup_group_permission') - migration_0009.migrate_app_permission(auth, app=app_instance_name) + from yunohost.tools import _get_migration_by_name + setup_group_permission = _get_migration_by_name("setup_group_permission") + setup_group_permission.migrate_app_permission(auth, app=app_instance_name) # Copy the app scripts to a writable temporary folder # FIXME : use 'install -Dm555' or something similar to what's done From 4d126ad73669b67886b3895522e1808fcc744fdb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 01:57:24 +0100 Subject: [PATCH 330/721] Misc simplification / explicit code --- src/yunohost/user.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index e1f706577..8e2fdb295 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -214,7 +214,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas # Create group for user and add to group 'all_users' user_group_add(auth, groupname=username, gid=uid, sync_perm=False) user_group_update(auth, groupname=username, add_user=username, force=True, sync_perm=False) - user_group_update(auth, 'all_users', add_user=username, force=True, sync_perm=True) + user_group_update(auth, groupname='all_users', add_user=username, force=True, sync_perm=True) # TODO: Send a welcome mail to user logger.success(m18n.n('user_created')) @@ -290,17 +290,17 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None, from yunohost.app import app_ssowatconf from yunohost.utils.password import assert_password_is_strong_enough - attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop'] - new_attr_dict = {} domains = domain_list(auth)['domains'] # Populate user informations + attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop'] result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch) if not result: raise YunohostError('user_unknown', user=username) user = result[0] # Get modifications from arguments + new_attr_dict = {} if firstname: new_attr_dict['givenName'] = firstname # TODO: Validate new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + user['sn'][0] @@ -594,7 +594,10 @@ def user_group_delete(operation_logger, auth, groupname, force=False, sync_perm= """ from yunohost.permission import permission_sync_to_user - if not force and (groupname == 'all_users' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']): + if not force \ + and (groupname == 'all_users' or + groupname == 'admins' or + groupname in user_list(auth, fields=['uid'])['users']): raise YunohostError('group_deletion_not_allowed', user=groupname) operation_logger.start() @@ -620,12 +623,11 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u from yunohost.permission import permission_sync_to_user - attrs_to_fetch = ['member'] - if (groupname == 'all_users' or groupname == 'admins') and not force: raise YunohostError('edit_group_not_allowed', group=groupname) # Populate group informations + attrs_to_fetch = ['member'] result = auth.search(base='ou=groups,dc=yunohost,dc=org', filter='cn=' + groupname, attrs=attrs_to_fetch) if not result: @@ -638,13 +640,13 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u else: group['member'] = [] - user_l = user_list(auth, ['uid'])['users'] + existing_users = user_list(auth, fields=['uid'])['users'] if add_user: if not isinstance(add_user, list): add_user = [add_user] for user in add_user: - if not user in user_l: + if not user in existing_users: raise YunohostError('user_unknown', user=user) userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if userDN in group['member']: @@ -700,16 +702,16 @@ def user_group_info(auth, groupname): if not result: raise YunohostError('group_unknown', group=groupname) - else: - group = result[0] - result_dict = { - 'groupname': group['cn'][0], - 'member': None - } - if 'member' in group: - result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']} - return result_dict + group = result[0] + + result_dict = { + 'groupname': group['cn'][0], + 'member': None + } + if 'member' in group: + result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']} + return result_dict # # Permission subcategory From 13c6c9e5004464dd6520e08b391218bcea63d556 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 02:07:17 +0100 Subject: [PATCH 331/721] Factorize validation from actual action --- src/yunohost/user.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 8e2fdb295..3e818f7ea 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -645,9 +645,12 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u if add_user: if not isinstance(add_user, list): add_user = [add_user] + for user in add_user: if not user in existing_users: raise YunohostError('user_unknown', user=user) + + for user in add_user: userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if userDN in group['member']: logger.warning(m18n.n('user_already_in_group', user=user, group=groupname)) @@ -656,10 +659,13 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u if remove_user: if not isinstance(remove_user, list): remove_user = [remove_user] + for user in remove_user: - userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if user == groupname: raise YunohostError('remove_user_of_group_not_allowed', user=user, group=groupname) + + for user in remove_user: + userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org" if 'member' in group and userDN in group['member']: new_group_list['member'].remove(userDN) else: From 1d4dfd52424e8f8d87ade6d07f8cbabdf880327a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 02:12:40 +0100 Subject: [PATCH 332/721] Clarify condition using a list --- locales/en.json | 2 +- src/yunohost/user.py | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6f9d28a40..d3bdeed04 100644 --- a/locales/en.json +++ b/locales/en.json @@ -218,7 +218,7 @@ "group_creation_failed": "Group creation failed for group '{group}'", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Group '{group} 'deletion failed", - "group_deletion_not_allowed": "You are not allowed to remove the main group of the user {user:s}", + "group_deletion_not_allowed": "The group {group:s} cannot be deleted manually.", "group_info_failed": "Group info failed", "group_unknown": "Group {group:s} unknown", "group_updated": "Group '{group}' updated", diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 3e818f7ea..d6981ed36 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -594,11 +594,9 @@ def user_group_delete(operation_logger, auth, groupname, force=False, sync_perm= """ from yunohost.permission import permission_sync_to_user - if not force \ - and (groupname == 'all_users' or - groupname == 'admins' or - groupname in user_list(auth, fields=['uid'])['users']): - raise YunohostError('group_deletion_not_allowed', user=groupname) + forbidden_groups = ["all_users", "admins"] + user_list(auth, fields=['uid'])['users'].keys() + if not force and groupname in forbidden_groups: + raise YunohostError('group_deletion_not_allowed', group=groupname) operation_logger.start() if not auth.remove('cn=%s,ou=groups' % groupname): @@ -640,7 +638,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u else: group['member'] = [] - existing_users = user_list(auth, fields=['uid'])['users'] + existing_users = user_list(auth, fields=['uid'])['users'].keys() if add_user: if not isinstance(add_user, list): From b782a1a6f0e138b665e7b847e78c1b8e0f6057c2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 02:57:00 +0100 Subject: [PATCH 333/721] Typos --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index d3bdeed04..7ec08cba2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -214,7 +214,7 @@ "group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled for app '{app:s}'", "group_already_disallowed": "Group '{group:s}' already has permissions '{permission:s}' disabled for app '{app:s}'", "group_name_already_exist": "Group {name:s} already exist", - "group_created": "Group '{group}' succesfully created", + "group_created": "Group '{group}' successfully created", "group_creation_failed": "Group creation failed for group '{group}'", "group_deleted": "Group '{group}' deleted", "group_deletion_failed": "Group '{group} 'deletion failed", @@ -416,7 +416,7 @@ "permission_not_found": "Permission '{permission:s}' not found for application {app:s}", "permission_name_not_valid": "Permission name '{permission:s}' not valid", "permission_update_failed": "Permission update failed", - "permission_generated": "The permission database has been updated.", + "permission_generated": "The permission database has been updated", "permission_updated": "Permission '{permission:s}' for app {app:s} updated", "permission_update_nothing_to_do": "No permissions to update", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", From bca4e39b2466d6bda68c489ef456a384b0bef991 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 5 Mar 2019 03:13:14 +0100 Subject: [PATCH 334/721] Make the PEP gods happy --- .../0009_setup_group_permission.py | 11 ++--- src/yunohost/permission.py | 47 +++++++++---------- src/yunohost/user.py | 37 +++++++++------ 3 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/yunohost/data_migrations/0009_setup_group_permission.py b/src/yunohost/data_migrations/0009_setup_group_permission.py index a1f4ca6ee..3c6958cac 100644 --- a/src/yunohost/data_migrations/0009_setup_group_permission.py +++ b/src/yunohost/data_migrations/0009_setup_group_permission.py @@ -1,7 +1,6 @@ import yaml import time import os -import shutil from moulinette import m18n from moulinette.core import init_authenticator @@ -9,8 +8,7 @@ from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger from yunohost.tools import Migration -from yunohost.utils.filesystem import free_space_in_directory, space_used_by_directory -from yunohost.user import user_list, user_group_add, user_group_update +from yunohost.user import user_group_add, user_group_update from yunohost.app import app_setting, app_list from yunohost.service import service_regen_conf from yunohost.permission import permission_add, permission_sync_to_user @@ -22,6 +20,7 @@ logger = getActionLogger('yunohost.migration') # Tools used also for restoration ################################################### + def migrate_LDAP_db(auth): logger.info(m18n.n("migration_0009_update_LDAP_database")) try: @@ -46,7 +45,7 @@ def migrate_LDAP_db(auth): logger.info(m18n.n("migration_0009_create_group")) - #Create a group for each yunohost user + # Create a group for each yunohost user user_list = auth.search('ou=users,dc=yunohost,dc=org', '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))', ['uid', 'uidNumber']) @@ -116,7 +115,7 @@ class MyMigration(Migration): 'user_rdn': 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'} auth = init_authenticator(AUTH_IDENTIFIER, AUTH_PARAMETERS) - #Update LDAP database + # Update LDAP database migrate_LDAP_db(auth) # Migrate permission @@ -126,7 +125,7 @@ class MyMigration(Migration): except Exception as e: logger.warn(m18n.n("migration_0009_migration_failed_trying_to_rollback")) os.system("systemctl stop slapd") - os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config + os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder) os.system("cp -r --preserve %s/ldap_db/. /var/lib/ldap/" % backup_folder) os.system("cp -r --preserve %s/apps_settings/. /etc/yunohost/apps/" % backup_folder) diff --git a/src/yunohost/permission.py b/src/yunohost/permission.py index aabf7f7a0..57de63f25 100644 --- a/src/yunohost/permission.py +++ b/src/yunohost/permission.py @@ -30,11 +30,12 @@ import random from moulinette import m18n from moulinette.utils.log import getActionLogger from yunohost.utils.error import YunohostError -from yunohost.user import user_list, user_group_list +from yunohost.user import user_list from yunohost.log import is_unit_operation logger = getActionLogger('yunohost.user') + def user_permission_list(auth, app=None, permission=None, username=None, group=None): """ List permission for specific application @@ -47,8 +48,6 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N """ - user_l = user_list(auth, ['uid'])['users'] - permission_attrs = [ 'cn', 'groupPermission', @@ -86,20 +85,20 @@ def user_permission_list(auth, app=None, permission=None, username=None, group=N for u in res['inheritPermission']: user_name.append(u.split("=")[1].split(",")[0]) - # Don't show the result if the user diffined a specific permission, user or group - if app and not app_name in app: + # Don't show the result if the user defined a specific permission, user or group + if app and app_name not in app: continue - if permission and not permission_name in permission: + if permission and permission_name not in permission: continue if username[0] and not set(username) & set(user_name): continue if group[0] and not set(group) & set(group_name): continue - if not app_name in permissions: + if app_name not in permissions: permissions[app_name] = {} - permissions[app_name][permission_name] = {'allowed_users':[], 'allowed_groups':[]} + permissions[app_name][permission_name] = {'allowed_users': [], 'allowed_groups': []} for g in group_name: permissions[app_name][permission_name]['allowed_groups'].append(g) for u in user_name: @@ -160,16 +159,16 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ # Validate that the group exist for g in add_group: - if not g in user_group_list(auth, ['cn'])['groups']: + if g not in user_group_list(auth, ['cn'])['groups']: raise YunohostError('group_unknown', group=g) for u in add_username: - if not u in user_list(auth, ['uid'])['users']: + if u not in user_list(auth, ['uid'])['users']: raise YunohostError('user_unknown', user=u) for g in del_group: - if not g in user_group_list(auth, ['cn'])['groups']: + if g not in user_group_list(auth, ['cn'])['groups']: raise YunohostError('group_unknown', group=g) for u in del_username: - if not u in user_list(auth, ['uid'])['users']: + if u not in user_list(auth, ['uid'])['users']: raise YunohostError('user_unknown', user=u) # Merge user and group (note that we consider all user as a group) @@ -193,7 +192,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ for a in app: for per in permission: permission_name = per + '.' + a - if not permission_name in result: + if permission_name not in result: raise YunohostError('permission_not_found', permission=per, app=a) new_per_dict[permission_name] = set() if 'groupPermission' in result[permission_name]: @@ -203,7 +202,7 @@ def user_permission_update(operation_logger, auth, app=[], permission=None, add_ if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]: raise YunohostError('need_define_permission_before') group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org' - if not group_name in new_per_dict[permission_name]: + if group_name not in new_per_dict[permission_name]: logger.warning(m18n.n('group_already_disallowed', permission=per, app=a, group=g)) else: new_per_dict[permission_name].remove(group_name) @@ -287,11 +286,11 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None, sync_ for a in app: for per in permission: permission_name = per + '.' + a - if not permission_name in result: + if permission_name not in result: raise YunohostError('permission_not_found', permission=per, app=a) if 'groupPermission' in result[permission_name] and 'cn=all_users,ou=groups,dc=yunohost,dc=org' in result[permission_name]['groupPermission']: - logger.warning(m18n.n('permission_already_clear', permission=per, app=a)) - continue + logger.warning(m18n.n('permission_already_clear', permission=per, app=a)) + continue if auth.update('cn=%s,ou=permission' % permission_name, default_permission): logger.success(m18n.n('permission_updated', permission=per, app=a)) else: @@ -311,7 +310,7 @@ def user_permission_clear(operation_logger, auth, app=[], permission=None, sync_ return user_permission_list(auth, app, permission) -@is_unit_operation(['permission','app']) +@is_unit_operation(['permission', 'app']) def permission_add(operation_logger, auth, app, permission, urls=None, default_allow=True, sync_perm=True): """ Create a new permission for a specific application @@ -325,7 +324,7 @@ def permission_add(operation_logger, auth, app, permission, urls=None, default_a from yunohost.domain import _normalize_domain_path # Validate uniqueness of permission in LDAP - permission_name = str(permission + '.' + app) # str(...) Fix encoding issue + permission_name = str(permission + '.' + app) # str(...) Fix encoding issue conflict = auth.get_conflict({ 'cn': permission_name }, base_dn='ou=permission,dc=yunohost,dc=org') @@ -366,7 +365,7 @@ def permission_add(operation_logger, auth, app, permission, urls=None, default_a raise YunohostError('permission_creation_failed') -@is_unit_operation(['permission','app']) +@is_unit_operation(['permission', 'app']) def permission_update(operation_logger, auth, app, permission, add_url=None, remove_url=None, sync_perm=True): """ Update a permission for a specific application @@ -380,7 +379,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem """ from yunohost.domain import _normalize_domain_path - permission_name = str(permission + '.' + app) # str(...) Fix encoding issue + permission_name = str(permission + '.' + app) # str(...) Fix encoding issue # Populate permission informations result = auth.search(base='ou=permission,dc=yunohost,dc=org', @@ -389,7 +388,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem raise YunohostError('permission_not_found', permission=permission, app=app) permission_obj = result[0] - if not 'URL' in permission_obj: + if 'URL' not in permission_obj: permission_obj['URL'] = [] url = set(permission_obj['URL']) @@ -412,7 +411,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem return user_permission_list(auth, app, permission) operation_logger.start() - if auth.update('cn=%s,ou=permission' % permission_name, {'cn':permission_name, 'URL': url}): + if auth.update('cn=%s,ou=permission' % permission_name, {'cn': permission_name, 'URL': url}): if sync_perm: permission_sync_to_user(auth) logger.success(m18n.n('permission_updated', permission=permission, app=app)) @@ -421,7 +420,7 @@ def permission_update(operation_logger, auth, app, permission, add_url=None, rem raise YunohostError('premission_update_failed') -@is_unit_operation(['permission','app']) +@is_unit_operation(['permission', 'app']) def permission_remove(operation_logger, auth, app, permission, force=False, sync_perm=True): """ Remove a permission for a specific application diff --git a/src/yunohost/user.py b/src/yunohost/user.py index d6981ed36..0fca858a3 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -209,7 +209,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas except subprocess.CalledProcessError: if not os.path.isdir('/home/{0}'.format(username)): logger.warning(m18n.n('user_home_creation_failed'), - exc_info=1) + exc_info=1) # Create group for user and add to group 'all_users' user_group_add(auth, groupname=username, gid=uid, sync_perm=False) @@ -220,7 +220,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas logger.success(m18n.n('user_created')) hook_callback('post_user_create', - args=[username, mail, password, firstname, lastname]) + args=[username, mail, password, firstname, lastname]) return {'fullname': fullname, 'username': username, 'mail': mail} @@ -469,10 +469,10 @@ def user_info(auth, username): else: raise YunohostError('user_info_failed') + # # Group subcategory # -# def user_group_list(auth, fields=None): """ List users @@ -485,9 +485,9 @@ def user_group_list(auth, fields=None): """ group_attr = { - 'cn' : 'groupname', - 'member' : 'members', - 'permission' : 'permission' + 'cn': 'groupname', + 'member': 'members', + 'permission': 'permission' } attrs = ['cn'] groups = {} @@ -531,11 +531,12 @@ def user_group_list(auth, fields=None): groupname = entry[group_attr['cn']] groups[groupname] = entry - return {'groups' : groups} + + return {'groups': groups} @is_unit_operation([('groupname', 'user')]) -def user_group_add(operation_logger, auth, groupname,gid=None, sync_perm=True): +def user_group_add(operation_logger, auth, groupname, gid=None, sync_perm=True): """ Create group @@ -645,7 +646,7 @@ def user_group_update(operation_logger, auth, groupname, add_user=None, remove_u add_user = [add_user] for user in add_user: - if not user in existing_users: + if user not in existing_users: raise YunohostError('user_unknown', user=user) for user in add_user: @@ -717,38 +718,44 @@ def user_group_info(auth, groupname): result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']} return result_dict + # # Permission subcategory # -# -import yunohost.permission def user_permission_list(auth, app=None, permission=None, username=None, group=None, sync_perm=True): + import yunohost.permission return yunohost.permission.user_permission_list(auth, app, permission, username, group) + @is_unit_operation([('app', 'user')]) def user_permission_add(operation_logger, auth, app, permission="main", username=None, group=None, sync_perm=True): + import yunohost.permission return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, - add_username=username, add_group=group, - del_username=None, del_group=None, - sync_perm=sync_perm) + add_username=username, add_group=group, + del_username=None, del_group=None, + sync_perm=sync_perm) + @is_unit_operation([('app', 'user')]) def user_permission_remove(operation_logger, auth, app, permission="main", username=None, group=None, sync_perm=True): + import yunohost.permission return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission, add_username=None, add_group=None, del_username=username, del_group=group, sync_perm=sync_perm) + @is_unit_operation([('app', 'user')]) def user_permission_clear(operation_logger, auth, app, permission=None, sync_perm=True): + import yunohost.permission return yunohost.permission.user_permission_clear(operation_logger, auth, app, permission, sync_perm=sync_perm) + # # SSH subcategory # -# import yunohost.ssh From f1e097a5bdb4bbc462fa823a42cb21e5e9fd7d1a Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 7 Mar 2019 11:25:32 +0100 Subject: [PATCH 335/721] Fix tab --- data/helpers.d/psql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index 705aeeb9a..f0be628ea 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -74,9 +74,9 @@ ynh_psql_create_db() { local db=$1 local user=${2:-} - local sql="CREATE DATABASE ${db};" + local sql="CREATE DATABASE ${db};" - # grant all privilegies to user + # grant all privilegies to user if [ -n "$user" ]; then sql+="GRANT ALL PRIVILEGES ON DATABASE ${db} TO ${user} WITH GRANT OPTION;" fi From 5e0f63eab488ef6430bfa6bd92f050baf93cc175 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Mar 2019 15:45:48 +0100 Subject: [PATCH 336/721] Reject app password if they contains { or } --- locales/en.json | 1 + src/yunohost/app.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/locales/en.json b/locales/en.json index f7a65a883..a70da7b82 100644 --- a/locales/en.json +++ b/locales/en.json @@ -380,6 +380,7 @@ "pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)", "pattern_positive_number": "Must be a positive number", "pattern_username": "Must be lower-case alphanumeric and underscore characters only", + "pattern_password_app": "Sorry, passwords should not contain the following characters: {forbidden_chars}", "port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections", "port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections", "port_available": "Port {port:d} is available", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index be0bb5a55..fa05ebe47 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2287,6 +2287,9 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None): else: raise YunohostError('app_argument_choice_invalid', name=arg_name, choices='yes, no, y, n, 1, 0') elif arg_type == 'password': + forbidden_chars = "{}" + if any(char in arg_value for char in forbidden_chars): + raise YunohostError('pattern_password_app', forbidden_chars=forbidden_chars) from yunohost.utils.password import assert_password_is_strong_enough assert_password_is_strong_enough('user', arg_value) args_dict[arg_name] = arg_value From 55e9df75d0dd97bbf1eece1e0a39bbc645d49df2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Mar 2019 17:03:59 +0100 Subject: [PATCH 337/721] Clean tmp directory if it not empty --- src/yunohost/backup.py | 64 +++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index f9505fb66..98758a57a 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -326,10 +326,19 @@ class BackupManager(): if not os.path.isdir(self.work_dir): filesystem.mkdir(self.work_dir, 0o750, parents=True, uid='admin') elif self.is_tmp_work_dir: - logger.debug("temporary directory for backup '%s' already exists", + + logger.debug("temporary directory for backup '%s' already exists... attempting to clean it", self.work_dir) - # FIXME May be we should clean the workdir here - raise YunohostError('backup_output_directory_not_empty') + + # Try to recursively unmount stuff (from a previously failed backup ?) + if _recursive_umount(self.work_dir) > 0: + raise YunohostError('backup_output_directory_not_empty') + else: + # If umount succeeded, remove the directory (we checked that + # we're in /home/yunohost.backup/tmp so that should be okay... + # c.f. method clean() which also does this) + filesystem.rm(self.work_dir, True, True) + filesystem.mkdir(self.work_dir, 0o750, parents=True, uid='admin') # # Backup target management # @@ -1514,34 +1523,12 @@ class BackupMethod(object): directories of the working directories """ if self.need_mount(): - if self._recursive_umount(self.work_dir) > 0: + if _recursive_umount(self.work_dir) > 0: raise YunohostError('backup_cleaning_failed') if self.manager.is_tmp_work_dir: filesystem.rm(self.work_dir, True, True) - def _recursive_umount(self, directory): - """ - Recursively umount sub directories of a directory - - Args: - directory -- a directory path - """ - mount_lines = subprocess.check_output("mount").split("\n") - - points_to_umount = [line.split(" ")[2] - for line in mount_lines - if len(line) >= 3 and line.split(" ")[2].startswith(directory)] - ret = 0 - for point in reversed(points_to_umount): - ret = subprocess.call(["umount", point]) - if ret != 0: - ret = 1 - logger.warning(m18n.n('backup_cleaning_failed', point)) - continue - - return ret - def _check_is_enough_free_space(self): """ Check free space in repository or output directory before to backup @@ -2011,6 +1998,7 @@ def backup_create(name=None, description=None, methods=[], # Check that output directory is empty if os.path.isdir(output_directory) and no_compress and \ os.listdir(output_directory): + raise YunohostError('backup_output_directory_not_empty') elif no_compress: raise YunohostError('backup_output_directory_required') @@ -2315,6 +2303,30 @@ def _call_for_each_path(self, callback, csv_path=None): callback(self, row['source'], row['dest']) +def _recursive_umount(directory): + """ + Recursively umount sub directories of a directory + + Args: + directory -- a directory path + """ + mount_lines = subprocess.check_output("mount").split("\n") + + points_to_umount = [line.split(" ")[2] + for line in mount_lines + if len(line) >= 3 and line.split(" ")[2].startswith(directory)] + + ret = 0 + for point in reversed(points_to_umount): + ret = subprocess.call(["umount", point]) + if ret != 0: + ret = 1 + logger.warning(m18n.n('backup_cleaning_failed', point)) + continue + + return ret + + def free_space_in_directory(dirpath): stat = os.statvfs(dirpath) return stat.f_frsize * stat.f_bavail From 135ac60d94d81e35ce790d0c0e7d7d037a490e03 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Mar 2019 17:04:40 +0100 Subject: [PATCH 338/721] Clarify arguments usage --- src/yunohost/backup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 98758a57a..5608c7478 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -337,7 +337,7 @@ class BackupManager(): # If umount succeeded, remove the directory (we checked that # we're in /home/yunohost.backup/tmp so that should be okay... # c.f. method clean() which also does this) - filesystem.rm(self.work_dir, True, True) + filesystem.rm(self.work_dir, recursive=True, force=True) filesystem.mkdir(self.work_dir, 0o750, parents=True, uid='admin') # @@ -913,7 +913,7 @@ class RestoreManager(): ret = subprocess.call(["umount", self.work_dir]) if ret != 0: logger.warning(m18n.n('restore_cleaning_failed')) - filesystem.rm(self.work_dir, True, True) + filesystem.rm(self.work_dir, recursive=True, force=True) # # Restore target manangement # From 7f779ddd1cf60ba808f5ca0569c121be5fb75e62 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 Mar 2019 17:10:01 +0100 Subject: [PATCH 339/721] This ambiguity about 'ret' definitely looks like a bug ... --- src/yunohost/backup.py | 10 +++++----- src/yunohost/tests/test_backuprestore.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 5608c7478..039944264 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -331,7 +331,7 @@ class BackupManager(): self.work_dir) # Try to recursively unmount stuff (from a previously failed backup ?) - if _recursive_umount(self.work_dir) > 0: + if not _recursive_umount(self.work_dir): raise YunohostError('backup_output_directory_not_empty') else: # If umount succeeded, remove the directory (we checked that @@ -1523,7 +1523,7 @@ class BackupMethod(object): directories of the working directories """ if self.need_mount(): - if _recursive_umount(self.work_dir) > 0: + if not _recursive_umount(self.work_dir): raise YunohostError('backup_cleaning_failed') if self.manager.is_tmp_work_dir: @@ -2316,15 +2316,15 @@ def _recursive_umount(directory): for line in mount_lines if len(line) >= 3 and line.split(" ")[2].startswith(directory)] - ret = 0 + everything_went_fine = True for point in reversed(points_to_umount): ret = subprocess.call(["umount", point]) if ret != 0: - ret = 1 + everything_went_fine = False logger.warning(m18n.n('backup_cleaning_failed', point)) continue - return ret + return everything_went_fine def free_space_in_directory(dirpath): diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 14c479d9a..73728240f 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -571,7 +571,7 @@ def test_backup_binds_are_readonly(monkeypatch): assert "Read-only file system" in output - if self._recursive_umount(self.work_dir) > 0: + if not _recursive_umount(self.work_dir): raise Exception("Backup cleaning failed !") self.clean() From a974d08897741e577353644acd28cad6c4727685 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Mar 2019 01:22:32 +0100 Subject: [PATCH 340/721] Fix use case of app_fetchlist with url=None --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index de759f04f..99d3784bc 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -97,7 +97,7 @@ def app_fetchlist(url=None, name=None): name -- Name of the list url -- URL of remote JSON list """ - if not url.endswith(".json"): + if url and not url.endswith(".json"): raise YunohostError("This is not a valid application list url. It should end with .json.") # If needed, create folder where actual appslists are stored From ea98cf557ef3c93fdb3f47304a5745d38418a894 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Mar 2019 19:53:56 +0100 Subject: [PATCH 341/721] Avoid miserably failing when attempting to create link in some edge case --- src/yunohost/backup.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 53919f2cc..80e13c273 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1633,9 +1633,18 @@ class BackupMethod(object): # 'NUMBER OF HARD LINKS > 1' see #1043 cron_path = os.path.abspath('/etc/cron') + '.' if not os.path.abspath(src).startswith(cron_path): - os.link(src, dest) - # Success, go to next file to organize - continue + try: + os.link(src, dest) + except Exception as e: + # This kind of situation may happen when src and dest are on different + # logical volume ... even though the st_dev check previously match... + # E.g. this happens when running an encrypted hard drive + # where everything is mapped to /dev/mapper/some-stuff + # yet there are different devices behind it or idk ... + logger.warning("Could not link %s to %s (%s) ... falling back to regular copy." % (src, dest, str(e))) + else: + # Success, go to next file to organize + continue # If mountbind or hardlink couldnt be created, # prepare a list of files that need to be copied From bf659db300bbe658bbe180c3a3deb306f8c0f6e9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 Mar 2019 19:54:26 +0100 Subject: [PATCH 342/721] Not sure why this stop working, maybe new pytest version or something... --- src/yunohost/tests/test_backuprestore.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 14c479d9a..54f31bcb2 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -42,7 +42,7 @@ def setup_function(function): assert len(backup_list()["archives"]) == 0 - markers = function.__dict__.keys() + markers = [m.name for m in function.__dict__.get("pytestmark",[])] if "with_wordpress_archive_from_2p4" in markers: add_archive_wordpress_from_2p4() @@ -82,7 +82,7 @@ def teardown_function(function): delete_all_backups() uninstall_test_apps_if_needed() - markers = function.__dict__.keys() + markers = [m.name for m in function.__dict__.get("pytestmark",[])] if "clean_opt_dir" in markers: shutil.rmtree("/opt/test_backup_output_directory") From ea8605db5ccab33c7ce64cb7ec92a4283471b46a Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Fri, 8 Mar 2019 23:32:23 +0100 Subject: [PATCH 343/721] [enh] Support php versions in ynh_add_fpm_config --- data/helpers.d/backend | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 1532601a8..fbebf278a 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -240,11 +240,21 @@ ynh_remove_nginx_config () { # Create a dedicated php-fpm config # -# usage: ynh_add_fpm_config +# usage: ynh_add_fpm_config [--phpversion=7.X] +# | arg: -v, --phpversion - Version of php to use. ynh_add_fpm_config () { + # Declare an array to define the options of this helper. + local legacy_args=v + declare -Ar args_array=( [v]=phpversion= ) + local phpversion + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + # Configure PHP-FPM 7.0 by default - local fpm_config_dir="/etc/php/7.0/fpm" - local fpm_service="php7.0-fpm" + phpversion="${phpversion:-7.0}" + + local fpm_config_dir="/etc/php/$phpversion/fpm" + local fpm_service="php${phpversion}-fpm" # Configure PHP-FPM 5 on Debian Jessie if [ "$(ynh_get_debian_release)" == "jessie" ]; then fpm_config_dir="/etc/php5/fpm" @@ -258,6 +268,7 @@ ynh_add_fpm_config () { ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf" ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf" ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf" + ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$phpversion" --target_file="$finalphpconf" sudo chown root: "$finalphpconf" ynh_store_file_checksum --file="$finalphpconf" From 2818abe5000e2a8d08624bf459c908f2c3b76696 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 Mar 2019 16:45:53 +0100 Subject: [PATCH 344/721] Update global.conf --- data/templates/nginx/plain/global.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/global.conf b/data/templates/nginx/plain/global.conf index ca8721afb..07f7c49ea 100644 --- a/data/templates/nginx/plain/global.conf +++ b/data/templates/nginx/plain/global.conf @@ -1,2 +1,2 @@ server_tokens off; -gzip_types text/css text/javascript application/javascript; +gzip off; From 945912cc983c9b2fe67cf9a6905f0a07d37d2c4c Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 10 Mar 2019 13:22:07 +0100 Subject: [PATCH 345/721] [fix] ynh_app_setting_get and ynh_app_setting_delete don't have a fourth argument. --- data/helpers.d/setting | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/setting b/data/helpers.d/setting index fb833281a..7aae6be7b 100644 --- a/data/helpers.d/setting +++ b/data/helpers.d/setting @@ -60,7 +60,7 @@ ynh_app_setting_delete() { # ynh_app_setting() { - ACTION="$1" APP="$2" KEY="$3" VALUE="$4" python - < Date: Sun, 10 Mar 2019 23:57:37 +0100 Subject: [PATCH 346/721] [microdecision] c.f. 25efab7 .. there's no way to include files in sshd_config --- .../0007_ssh_conf_managed_by_yunohost_step1.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py index 080cc0163..d188ff024 100644 --- a/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py +++ b/src/yunohost/data_migrations/0007_ssh_conf_managed_by_yunohost_step1.py @@ -49,10 +49,6 @@ class MyMigration(Migration): if dsa: settings_set("service.ssh.allow_deprecated_dsa_hostkey", True) - # Create sshd_config.d dir - if not os.path.exists(SSHD_CONF + '.d'): - mkdir(SSHD_CONF + '.d', 0o755, uid='root', gid='root') - # Here, we make it so that /etc/ssh/sshd_config is managed # by the regen conf (in particular in the case where the # from_script flag is present - in which case it was *not* From 3550be63f2e5cb556a0c080ca58b62836f052d84 Mon Sep 17 00:00:00 2001 From: Christophe Vuillot Date: Mon, 11 Mar 2019 14:53:40 +0100 Subject: [PATCH 347/721] Added a python script (yunohost_completion.py) which generates a bash completion file for the yunohost command based on yunohost.yml, in data/actionsmap Added the output of the script in data/bash-completion.d/yunohost_completion This is probably not the correct place for the script and the generation should be done at some other time and place also. --- data/actionsmap/yunohost_completion.py | 84 ++++++++++++++++++++++ data/bash-completion.d/yunohost_completion | 77 ++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 data/actionsmap/yunohost_completion.py create mode 100644 data/bash-completion.d/yunohost_completion diff --git a/data/actionsmap/yunohost_completion.py b/data/actionsmap/yunohost_completion.py new file mode 100644 index 000000000..9b5472837 --- /dev/null +++ b/data/actionsmap/yunohost_completion.py @@ -0,0 +1,84 @@ +""" +Simple automated generation of a bash_completion file +for yunohost command from the actionsmap. + +Generates a bash completion file assuming the structure +`yunohost domain action` +adds `--help` at the end if one presses [tab] again. + +author: Christophe Vuillot +""" +import yaml + +ACTIONSMAP_FILE = 'yunohost.yml' +BASH_COMPLETION_FILE = '../bash-completion.d/yunohost_completion' + +with open(ACTIONSMAP_FILE, 'r') as stream: + + # Getting the dictionary containning what actions are possible per domain + OPTION_TREE = yaml.load(stream) + DOMAINS = [str for str in OPTION_TREE.keys() if not str.startswith('_')] + DOMAINS_STR = '"{}"'.format(' '.join(DOMAINS)) + ACTIONS_DICT = {} + for domain in DOMAINS: + ACTIONS = [str for str in OPTION_TREE[domain]['actions'].keys() + if not str.startswith('_')] + ACTIONS_STR = '"{}"'.format(' '.join(ACTIONS)) + ACTIONS_DICT[domain] = ACTIONS_STR + + with open(BASH_COMPLETION_FILE, 'w') as generated_file: + + # header of the file + generated_file.write('#\n') + generated_file.write('# completion for yunohost\n') + generated_file.write('# automatically generated from the actionsmap\n') + generated_file.write('#\n\n') + + # Start of the completion function + generated_file.write('_yunohost_completion()\n') + generated_file.write('{\n') + + # Defining local variable for previously and currently typed words + generated_file.write('\tlocal cur prev opts narg\n') + generated_file.write('\tCOMPREPLY=()\n\n') + generated_file.write('\t# the number of words already typed\n') + generated_file.write('\tnarg=${#COMP_WORDS[@]}\n\n') + generated_file.write('\t# the current word being typed\n') + generated_file.write('\tcur="${COMP_WORDS[COMP_CWORD]}"\n\n') + generated_file.write('\t# the last typed word\n') + generated_file.write('\tprev="${COMP_WORDS[COMP_CWORD-1]}"\n\n') + + # If one is currently typing a domain then match with the domain list + generated_file.write('\t# If one is currently typing a domain,\n') + generated_file.write('\t# match with domains\n') + generated_file.write('\tif [[ $narg == 2 ]]; then\n') + generated_file.write('\t\topts={}\n'.format(DOMAINS_STR)) + generated_file.write('\tfi\n\n') + + # If one is currently typing an action then match with the action list + # of the previously typed domain + generated_file.write('\t# If one already typed a domain,\n') + generated_file.write('\t# match the actions of that domain\n') + generated_file.write('\tif [[ $narg == 3 ]]; then\n') + for domain in DOMAINS: + generated_file.write('\t\tif [[ $prev == "{}" ]]; then\n'.format(domain)) + generated_file.write('\t\t\topts={}\n'.format(ACTIONS_DICT[domain])) + generated_file.write('\t\tfi\n') + generated_file.write('\tfi\n\n') + + # If both domain and action have been typed or the domain + # was not recognized propose --help (only once) + generated_file.write('\t# If no options were found propose --help\n') + generated_file.write('\tif [ -z "$opts" ]; then\n') + generated_file.write('\t\tif [[ $prev != "--help" ]]; then\n') + generated_file.write('\t\t\topts=( --help )\n') + generated_file.write('\t\tfi\n') + generated_file.write('\tfi\n') + + # generate the completion list from the possible options + generated_file.write('\tCOMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )\n') + generated_file.write('\treturn 0\n') + generated_file.write('}\n\n') + + # Add the function to bash completion + generated_file.write('complete -F _yunohost_completion yunohost') diff --git a/data/bash-completion.d/yunohost_completion b/data/bash-completion.d/yunohost_completion new file mode 100644 index 000000000..715073475 --- /dev/null +++ b/data/bash-completion.d/yunohost_completion @@ -0,0 +1,77 @@ +# +# completion for yunohost +# automatically generated from the actionsmap +# + +_yunohost_completion() +{ + local cur prev opts narg + COMPREPLY=() + + # the number of words already typed + narg=${#COMP_WORDS[@]} + + # the current word being typed + cur="${COMP_WORDS[COMP_CWORD]}" + + # the last typed word + prev="${COMP_WORDS[COMP_CWORD-1]}" + + # If one is currently typing a domain, + # match with domains + if [[ $narg == 2 ]]; then + opts="user domain log service settings firewall backup app hook dyndns tools monitor" + fi + + # If one already typed a domain, + # match the actions of that domain + if [[ $narg == 3 ]]; then + if [[ $prev == "user" ]]; then + opts="info create list update delete" + fi + if [[ $prev == "domain" ]]; then + opts="cert-install cert-status list remove url-available add dns-conf cert-renew" + fi + if [[ $prev == "log" ]]; then + opts="list display" + fi + if [[ $prev == "service" ]]; then + opts="status enable reload_or_restart log start stop remove reload add disable regen-conf restart" + fi + if [[ $prev == "settings" ]]; then + opts="reset set list reset-all get" + fi + if [[ $prev == "firewall" ]]; then + opts="reload allow stop list upnp disallow" + fi + if [[ $prev == "backup" ]]; then + opts="info restore create list delete" + fi + if [[ $prev == "app" ]]; then + opts="map checkurl install makedefault checkport listlists change-url removelist info change-label upgrade fetchlist clearaccess ssowatconf list remove register-url removeaccess setting initdb debug addaccess" + fi + if [[ $prev == "hook" ]]; then + opts="info callback add exec list remove" + fi + if [[ $prev == "dyndns" ]]; then + opts="subscribe update installcron removecron" + fi + if [[ $prev == "tools" ]]; then + opts="upgrade ldapinit postinstall maindomain update reboot shell adminpw shutdown diagnosis port-available" + fi + if [[ $prev == "monitor" ]]; then + opts="enable network show-stats update-stats disk system disable" + fi + fi + + # If no options were found propose --help + if [ -z "$opts" ]; then + if [[ $prev != "--help" ]]; then + opts=( --help ) + fi + fi + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 +} + +complete -F _yunohost_completion yunohost \ No newline at end of file From 1d37dd4fa28a7529dd438f1aaa544d2551894b0e Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Wed, 13 Mar 2019 13:32:50 +0100 Subject: [PATCH 348/721] [fix] Missing import --- src/yunohost/tests/test_backuprestore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 73728240f..893925e83 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -10,7 +10,7 @@ from moulinette import m18n from moulinette.core import init_authenticator from yunohost.app import app_install, app_remove, app_ssowatconf from yunohost.app import _is_installed -from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete +from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount from yunohost.domain import _get_maindomain from yunohost.utils.error import YunohostError From 73aefc6bc8aaf3f01e983300afad1124d7b9eecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9?= Date: Tue, 5 Feb 2019 20:09:15 +0000 Subject: [PATCH 349/721] Translated using Weblate (Russian) Currently translated at 9.4% (44 of 465 strings) --- locales/ru.json | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index 2658446bc..306a8763a 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -6,5 +6,41 @@ "app_already_installed": "{app:s} уже установлено", "app_already_installed_cant_change_url": "Это приложение уже установлено. URL не может быть изменен только с помощью этой функции. Изучите `app changeurl`, если это доступно.", "app_argument_choice_invalid": "Неверный выбор для аргумента '{name:s}', Это должно быть '{choices:s}'", - "app_argument_invalid": "Недопустимое значение аргумента '{name:s}': {error:s}'" + "app_argument_invalid": "Недопустимое значение аргумента '{name:s}': {error:s}'", + "app_already_up_to_date": "{app:s} уже обновлено", + "app_argument_required": "Аргумент '{name:s}' необходим", + "app_change_no_change_url_script": "Приложение {app_name:s} не поддерживает изменение URL, вы должны обновить его.", + "app_change_url_identical_domains": "Старый и новый domain/url_path идентичны ('{domain:s}{path:s}'), ничего делать не надо.", + "app_change_url_no_script": "Приложение '{app_name:s}' не поддерживает изменение url. Наверное, вам нужно обновить приложение.", + "app_change_url_success": "Успешно изменён {app:s} url на {domain:s}{path:s}", + "app_extraction_failed": "Невозможно извлечь файлы для инсталляции", + "app_id_invalid": "Неправильный id приложения", + "app_incompatible": "Приложение {app} несовместимо с вашей версией YonoHost", + "app_install_files_invalid": "Неправильные файлы инсталляции", + "app_location_already_used": "Приложение '{app}' уже установлено по этому адресу ({path})", + "app_location_install_failed": "Невозможно установить приложение в это место, потому что оно конфликтует с приложением, '{other_app}' установленном на '{other_path}'", + "app_location_unavailable": "Этот url отсутствует или конфликтует с уже установленным приложением или приложениями: {apps:s}", + "app_manifest_invalid": "Недопустимый манифест приложения: {error}", + "app_no_upgrade": "Нет приложений, требующих обновления", + "app_not_correctly_installed": "{app:s} , кажется, установлены неправильно", + "app_not_installed": "{app:s} не установлены", + "app_not_properly_removed": "{app:s} удалены неправильно", + "app_package_need_update": "Пакет приложения {app} должен быть обновлён в соответствии с изменениями YonoHost", + "app_removed": "{app:s} удалено", + "app_requirements_checking": "Проверяю необходимые пакеты для {app}...", + "app_sources_fetch_failed": "Невозможно получить исходные файлы", + "app_unknown": "Неизвестное приложение", + "app_upgrade_app_name": "Обновление приложения {app}...", + "app_upgrade_failed": "Невозможно обновить {app:s}", + "app_upgrade_some_app_failed": "Невозможно обновить некоторые приложения", + "app_upgraded": "{app:s} обновлено", + "appslist_corrupted_json": "Не могу загрузить список приложений. Кажется, {filename:s} поврежден.", + "appslist_fetched": "Был выбран список приложений {appslist:s}", + "appslist_name_already_tracked": "Уже есть зарегистрированный список приложений по имени {name:s}.", + "appslist_removed": "Список приложений {appslist:s} удалён", + "appslist_retrieve_bad_format": "Неверный файл списка приложений{appslist:s}", + "appslist_retrieve_error": "Невозможно получить список удаленных приложений {appslist:s}: {error:s}", + "appslist_unknown": "Список приложений {appslist:s} неизвестен.", + "appslist_url_already_tracked": "Это уже зарегистрированный список приложений с url{url:s}.", + "installation_complete": "Установка завершена" } From 4ed63926804086fff2a037fb522796b0dcb145e4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 Mar 2019 16:26:18 +0100 Subject: [PATCH 350/721] Update changelog for 3.5.0 testing --- debian/changelog | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/debian/changelog b/debian/changelog index 7be4212fe..f8485e020 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,54 @@ +yunohost (3.5.0) testing; urgency=low + + Core + ---- + + - [fix] Disable gzip entirely to avoid BREACH attacks (#675) + - [fix] Backup tests were broken (#673) + - [fix] Backup fails because output directory not empty (#672) + - [fix] Reject app password if they contains { or } (#671) + - [enh] Allow `display_text` 'fake' argument in manifest.json (#669) + - [fix] Optimize dyndns requests (#662) + - [enh] Don't add Strict-Transport-Security header in nginx conf if using a selfsigned cert (#661) + - [enh] Add apt-transport-https to dependencies (#658) + - [enh] Cache results from meltdown vulnerability checker (#656) + - [enh] Ensure the tar file is closed during the backup (#655) + - [enh] Be able to define hook to trigger when changing a setting (#654) + - [enh] Assert dpkg is not broken before app install (#652) + - [fix] Loading only one helper file leads to errors because missing getopts (#651) + - [enh] Improve / add some messages to improve UX (#650) + - [enh] Reload fail2ban instead of restart (#649) + - [enh] Add IPv6 resolvers from diyisp.org to resolv.dnsmasq.conf (#639) + - [fix] Remove old SMTP port (465) from fail2ban jail.conf (#637) + - [enh] Improve protection against indexation from the robots. (#622) + - [enh] Allow hooks to return data (#526) + - [fix] Do not make version number available from web API to unauthenticated users (#291) + - [i18n] Improve Russian and Chinese (Mandarin) translations + + App helpers + ----------- + + - [enh] Optimize app setting helpers (#663, #676) + - [enh] Handle `ynh_install_nodejs` for arm64 / aarch64 (#660) + - [enh] Update postgresql helpers (#657) + - [enh] Print diff of files when backup by `ynh_backup_if_checksum_is_different` (#648) + - [enh] Add app debugger helper (#647) + - [fix] Escape double quote before eval in getopts (#646) + - [fix] `ynh_local_curl` not using the right url in some cases (#644) + - [fix] Get rid of annoying 'unable to initialize frontend' messages (#643) + - [enh] Check if dpkg is not broken when calling `ynh_wait_dpkg_free` (#638) + - [enh] Warn the packager that `ynh_secure_remove` should be used with only one arg… (#635, #642) + - [enh] Add `ynh_script_progression` helper (#634) + - [enh] Add `ynh_systemd_action` helper (#633) + - [enh] Allow to dig deeper into an archive with `ynh_setup_source` (#630) + - [enh] Use getops (#561) + - [enh] Add `ynh_check_app_version_changed` helper (#521) + - [enh] Add fail2ban helpers (#364) + + Contributors: Alexandre Aubin, Jimmy Monin, Josué Tille, Kayou, Laurent Peuch, Lukas Fülling, Maniack Crudelis, Taekiro, frju365, ljf, opi, yalh76, Алексей + + -- Alexandre Aubin Wed, 13 Mar 2019 16:10:00 +0000 + yunohost (3.4.2.4) stable; urgency=low - [fix] Meltdown vulnerability checker something outputing trash instead of pure json From 485cb4b5c75a7a74ab95104ffdc6eb4c7b00bfeb Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Wed, 13 Mar 2019 18:42:07 +0100 Subject: [PATCH 351/721] [fix] Delete properly group as well as user --- data/helpers.d/user | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index d716bf03b..b28ee490a 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -63,6 +63,21 @@ ynh_system_user_exists() { getent passwd "$username" &>/dev/null } +# Check if a group exists on the system +# +# usage: ynh_system_group_exists --group=group +# | arg: -g, --group - the group to check +ynh_system_group_exists() { + # Declare an array to define the options of this helper. + local legacy_args=g + declare -Ar args_array=( [g]=group= ) + local group + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + getent group "$group" &>/dev/null +} + # Create a system user # # examples: @@ -116,11 +131,21 @@ ynh_system_user_delete () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - if ynh_system_user_exists "$username" # Check if the user exists on the system + # Check if the user exists on the system + if ynh_system_user_exists "$username" then echo "Remove the user $username" >&2 - sudo userdel $username + deluser $username else echo "The user $username was not found" >&2 fi + + # Check if the group exists on the system + if ynh_system_group_exists "$username" + then + echo "Remove the group $username" >&2 + delgroup $username + else + echo "The group $username was not found" >&2 + fi } From ab574bb218a414f975651ac5b386bed9d9317363 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Wed, 13 Mar 2019 18:57:27 +0100 Subject: [PATCH 352/721] [enh] reload-or-restart instead of reload --- data/helpers.d/system | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index bfc20cb33..fd552db7f 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -59,7 +59,7 @@ ynh_get_debian_release () { # Start (or other actions) a service, print a log in case of failure and optionnaly wait until the service is completely started # # usage: ynh_systemd_action [-n service_name] [-a action] [ [-l "line to match"] [-p log_path] [-t timeout] [-e length] ] -# | arg: -n, --service_name= - Name of the service to reload. Default : $app +# | arg: -n, --service_name= - Name of the service to start. Default : $app # | arg: -a, --action= - Action to perform with systemctl. Default: start # | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. # If not defined it don't wait until the service is completely started. @@ -107,6 +107,12 @@ ynh_systemd_action() { fi ynh_print_info --message="${action^} the service $service_name" + + # Use reload-or-restart instead of reload. So it wouldn't fail if the service isn't running. + if [ "$action" == "reload" ]; then + action="reload-or-restart" + fi + systemctl $action $service_name \ || ( journalctl --no-pager --lines=$length -u $service_name >&2 \ ; test -e "$log_path" && echo "--" >&2 && tail --lines=$length "$log_path" >&2 \ From a8f88e7232ca39005e9d78e45481ee8bb1e6e771 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 Mar 2019 19:11:49 +0100 Subject: [PATCH 353/721] [yolo] gzip off in global.conf breaks everything because conflict with gzip on; in nginx.conf ... Moving it to server blocs --- data/templates/nginx/plain/global.conf | 1 - data/templates/nginx/plain/yunohost_admin.conf | 4 ++++ data/templates/nginx/server.tpl.conf | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/global.conf b/data/templates/nginx/plain/global.conf index 07f7c49ea..b3a5f356a 100644 --- a/data/templates/nginx/plain/global.conf +++ b/data/templates/nginx/plain/global.conf @@ -1,2 +1 @@ server_tokens off; -gzip off; diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index 2493e4033..ff61b8638 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -51,6 +51,10 @@ server { more_set_headers "X-Permitted-Cross-Domain-Policies : none"; more_set_headers "X-Frame-Options : SAMEORIGIN"; + # Disable gzip to protect against BREACH + # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) + gzip off; + location / { return 302 https://$http_host/yunohost/admin; } diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 43d38ca98..d8793ef05 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -71,6 +71,10 @@ server { resolver_timeout 5s; {% endif %} + # Disable gzip to protect against BREACH + # Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!) + gzip off; + access_by_lua_file /usr/share/ssowat/access.lua; include /etc/nginx/conf.d/{{ domain }}.d/*.conf; From e3d8929dec650d6980e64a3b17f000150ac27c6e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 Mar 2019 19:24:00 +0100 Subject: [PATCH 354/721] Update changelog for 3.5.0.1 --- debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/debian/changelog b/debian/changelog index f8485e020..7290e935d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +yunohost (3.5.0.1) testing; urgency=low + + - [fix] #675 introduced a bug in nginx conf ... + + -- Alexandre Aubin Wed, 13 Mar 2019 19:23:00 +0000 + yunohost (3.5.0) testing; urgency=low Core From 1507e11e6eb84551e4e44ce4daf5241d536e22dd Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Wed, 13 Mar 2019 19:32:10 +0100 Subject: [PATCH 355/721] Do not warn if group isn't exist --- data/helpers.d/user | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/helpers.d/user b/data/helpers.d/user index b28ee490a..5a65f7642 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -145,7 +145,5 @@ ynh_system_user_delete () { then echo "Remove the group $username" >&2 delgroup $username - else - echo "The group $username was not found" >&2 fi } From e24890c2f2f01bd6f3a934bf30ee713ad6503564 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 Mar 2019 03:48:35 +0100 Subject: [PATCH 356/721] Update changelog for 3.5.0.2 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 7290e935d..444d797e1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +yunohost (3.5.0.2) testing; urgency=low + + - [fix] Make sure that `ynh_system_user_delete` also deletes the group (#680) + - [enh] `ynh_systemd_action` : reload-or-restart instead of just reload (#681) + + Last minute fixes by Maniack ;) + + -- Alexandre Aubin Thu, 14 Mar 2019 03:45:00 +0000 + yunohost (3.5.0.1) testing; urgency=low - [fix] #675 introduced a bug in nginx conf ... From 23d7a0005d6f7c17825473b50bd04bf89181aaa1 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 16 Mar 2019 16:05:32 +0100 Subject: [PATCH 357/721] Escape $ in getopts --- data/helpers.d/getopts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index 7055325f1..891fdaaeb 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -152,6 +152,8 @@ ynh_handle_getopts_args () { fi # Escape double quote to prevent any interpretation during the eval all_args[$i]="${all_args[$i]//\"/\\\"}" + # Escape $ as well to prevent the string following it to be seen as a variable. + all_args[$i]="${all_args[$i]//$/\\\$}" eval ${option_var}+=\"${all_args[$i]}\" shift_value=$(( shift_value + 1 )) @@ -193,6 +195,8 @@ ynh_handle_getopts_args () { # Escape double quote to prevent any interpretation during the eval arguments[$i]="${arguments[$i]//\"/\\\"}" + # Escape $ as well to prevent the string following it to be seen as a variable. + all_args[$i]="${all_args[$i]//$/\\\$}" # Store each value given as argument in the corresponding variable # The values will be stored in the same order than $args_array From ad748a75f62490386c7ca97b92191d3ee2d3fa0d Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 16 Mar 2019 16:15:54 +0100 Subject: [PATCH 358/721] eval is useful and working before being evil... --- data/helpers.d/getopts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index 891fdaaeb..90db671c4 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -155,6 +155,13 @@ ynh_handle_getopts_args () { # Escape $ as well to prevent the string following it to be seen as a variable. all_args[$i]="${all_args[$i]//$/\\\$}" + # For the record. + # We're using eval here to get the content of the variable stored itself as simple text in $option_var... + # Other ways to get that content would be to use either ${!option_var} or declare -g ${option_var} + # But... ${!option_var} can't be used as left part of an assignation. + # declare -g ${option_var} will create a local variable (despite -g !) and will not be available for the helper itself. + # So... Stop fucking arguing each time that eval is evil... Go find an other working solution if you can find one! + eval ${option_var}+=\"${all_args[$i]}\" shift_value=$(( shift_value + 1 )) fi From 652edc204cbf9fbcd7fc9d273d429dc05a0eef7f Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sat, 16 Mar 2019 16:53:07 +0100 Subject: [PATCH 359/721] Syntax fix --- data/helpers.d/getopts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index 90db671c4..70d4a13d3 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -203,7 +203,7 @@ ynh_handle_getopts_args () { # Escape double quote to prevent any interpretation during the eval arguments[$i]="${arguments[$i]//\"/\\\"}" # Escape $ as well to prevent the string following it to be seen as a variable. - all_args[$i]="${all_args[$i]//$/\\\$}" + arguments[$i]="${arguments[$i]//$/\\\$}" # Store each value given as argument in the corresponding variable # The values will be stored in the same order than $args_array From 88deea7cec5032536ca23ddaf546bd982e1ce688 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sat, 16 Mar 2019 21:34:46 +0000 Subject: [PATCH 360/721] Dunno why those changes were not actually commited... --- locales/ca.json | 61 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index 6c06d55b3..bfad4d2bd 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -21,5 +21,64 @@ "app_location_already_used": "L'aplicació '{app}' ja està instal·lada en aquest camí ({path})", "app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' per defecte en el domini {domain} ja que ja és utilitzat per una altra aplicació '{other_app}'", "app_location_install_failed": "No s'ha pogut instal·lar l'aplicació en aquest camí ja que entra en conflicte amb l'aplicació '{other_app}' ja instal·lada a '{other_path}'", - "app_location_unavailable": "Aquesta url no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}" + "app_location_unavailable": "Aquesta url no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}", + "app_manifest_invalid": "Manifest d'aplicació incorrecte: {error}", + "app_no_upgrade": "No hi ha cap aplicació per actualitzar", + "app_not_correctly_installed": "{app:s} sembla estar mal instal·lada", + "app_not_installed": "{app:s} no està instal·lada", + "app_not_properly_removed": "{app:s} no s'ha pogut suprimir correctament", + "app_package_need_update": "El paquet de l'aplicació {app} ha de ser actualitzat per poder seguir els canvis de YunoHost", + "app_removed": "{app:s} ha estat suprimida", + "app_requirements_checking": "Verificació dels paquets requerits per {app}", + "app_requirements_failed": "No es poden satisfer els requeriments per {app}: {error}", + "app_requirements_unmeet": "No es compleixen els requeriments per {app}, el paquet {pkgname} ({version}) ha de ser {spec}", + "app_sources_fetch_failed": "No s'han pogut carregar els fitxers font", + "app_unknown": "Aplicació desconeguda", + "app_unsupported_remote_type": "El tipus remot utilitzat per l'aplicació no està suportat", + "app_upgrade_app_name": "Actualitzant l'aplicació {app}...", + "app_upgrade_failed": "No s'ha pogut actualitzar {app:s}", + "app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions", + "app_upgraded": "{app:s} ha estat actualitzada", + "appslist_corrupted_json": "No s'han pogut carregar les llistes d'aplicacions. Sembla que {filename:s} està danyat.", + "appslist_could_not_migrate": "No s'ha pogut migrar la llista d'aplicacions {appslist:s}! No s'ha pogut analitzar la URL... L'antic cronjob s'ha guardat a {bkp_file:s}.", + "appslist_fetched": "S'ha descarregat la llista d'aplicacions {appslist:s} correctament", + "appslist_migrating": "Migrant la llista d'aplicacions {appslist:s} ...", + "appslist_name_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb el nom {name:s}.", + "appslist_removed": "S'ha eliminat la llista d'aplicacions {appslist:s}", + "appslist_retrieve_bad_format": "L'arxiu obtingut per la llista d'aplicacions {appslist:s} no és vàlid", + "appslist_retrieve_error": "No s'ha pogut obtenir la llista d'aplicacions remota {appslist:s}: {error:s}", + "appslist_unknown": "La llista d'aplicacions {appslist:s} es desconeguda.", + "appslist_url_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb al URL {url:s}.", + "ask_current_admin_password": "Contrasenya d'administrador actual", + "ask_email": "Correu electrònic", + "ask_firstname": "Nom", + "ask_lastname": "Cognom", + "ask_list_to_remove": "Llista per a suprimir", + "ask_main_domain": "Domini principal", + "ask_new_admin_password": "Nova contrasenya d'administrador", + "ask_password": "Contrasenya", + "ask_path": "Camí", + "backup_abstract_method": "Encara no s'ha implementat aquest mètode de copia de seguretat", + "backup_action_required": "S'ha d'especificar què s'ha de guardar", + "backup_app_failed": "No s'ha pogut fer la còpia de seguretat de l'aplicació \"{app:s}\"", + "backup_applying_method_borg": "Enviant tots els fitxers de la còpia de seguretat al repositori borg-backup...", + "backup_applying_method_copy": "Còpia de tots els fitxers a la còpia de seguretat...", + "backup_applying_method_custom": "Crida del mètode de còpia de seguretat personalitzat \"{method:s}\"...", + "backup_applying_method_tar": "Creació de l'arxiu tar de la còpia de seguretat...", + "backup_archive_app_not_found": "L'aplicació \"{app:s}\" no es troba dins l'arxiu de la còpia de seguretat", + "backup_archive_broken_link": "No s'ha pogut accedir a l'arxiu de la còpia de seguretat (enllaç invàlid cap a {path:s})", + "backup_archive_mount_failed": "No s'ha pogut carregar l'arxiu de la còpia de seguretat", + "backup_archive_name_exists": "Ja hi ha una còpia de seguretat amb aquest nom", + "backup_archive_name_unknown": "Còpia de seguretat local \"{name:s}\" desconeguda", + "backup_archive_open_failed": "No s'ha pogut obrir l'arxiu de la còpia de seguretat", + "backup_archive_system_part_not_available": "La part \"{part:s}\" del sistema no està disponible en aquesta copia de seguretat", + "backup_archive_writing_error": "No es poden afegir arxius a l'arxiu comprimit de la còpia de seguretat", + "backup_ask_for_copying_if_needed": "Alguns fitxers no s'han pogut preparar per la còpia de seguretat utilitzant el mètode que evita malgastar espai del sistema temporalment. Per fer la còpia de seguretat, s'han d'utilitzar {size:s}MB temporalment. Hi esteu d'acord?", + "backup_borg_not_implemented": "El mètode de còpia de seguretat Borg encara no està implementat", + "backup_cant_mount_uncompress_archive": "No es pot carregar en mode de lectura només el directori de l'arxiu descomprimit", + "backup_cleaning_failed": "No s'ha pogut netejar el directori temporal de la còpia de seguretat", + "backup_copying_to_organize_the_archive": "Copiant {size:s}MB per organitzar l'arxiu", + "backup_couldnt_bind": "No es pot lligar {src:s} amb {dest:s}.", + "backup_created": "S'ha creat la còpia de seguretat", + "backup_creating_archive": "Creant l'arxiu de la còpia de seguretat" } From 5ec499e75827fb6158e4c403cf31756c3ae9dfef Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 17 Mar 2019 12:46:20 +0000 Subject: [PATCH 361/721] Added translation using Weblate (Greek) --- locales/el.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/el.json diff --git a/locales/el.json b/locales/el.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/el.json @@ -0,0 +1 @@ +{} From aa0c1985d08a5ce4c236b6d2d6ce180f3ddd4459 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 17 Mar 2019 12:47:04 +0000 Subject: [PATCH 362/721] Added translation using Weblate (Polish) --- locales/pl.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/pl.json diff --git a/locales/pl.json b/locales/pl.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/pl.json @@ -0,0 +1 @@ +{} From b5909d7d3445fc845daf5f11444b62ca20f6ebe2 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 17 Mar 2019 12:47:38 +0000 Subject: [PATCH 363/721] Added translation using Weblate (Swedish) --- locales/sv.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/sv.json diff --git a/locales/sv.json b/locales/sv.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/sv.json @@ -0,0 +1 @@ +{} From b9fe9cb3a569e6a451f733142726db979194ba12 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 17 Mar 2019 12:49:23 +0000 Subject: [PATCH 364/721] Added translation using Weblate (Chinese (Simplified)) --- locales/zh_Hans.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 locales/zh_Hans.json diff --git a/locales/zh_Hans.json b/locales/zh_Hans.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/locales/zh_Hans.json @@ -0,0 +1 @@ +{} From c21a45590946feb69f8b699bedb62d25ae2d4a0f Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 17 Mar 2019 22:23:16 +0100 Subject: [PATCH 365/721] Dump ynh log if an app script fails --- data/helpers.d/system | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/data/helpers.d/system b/data/helpers.d/system index fd552db7f..87529c733 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -27,6 +27,32 @@ ynh_exit_properly () { echo -e "!!\n $app's script has encountered an error. Its execution was cancelled.\n!!" >&2 + # Unset xtrace to not spoil the log + set +x + local ynh_log="/var/log/yunohost/yunohost-cli.log" + # Wait for the log to be fill with the data until the crash. + local timeout=0 + while ! tail --lines=20 "$ynh_log" | grep --quiet "ynh_exit_properly" + do + ((timeout++)) + if [ $timeout -eq 500 ]; then + break + fi + done + set -x + + echo -e "\e[34m\e[1mLog extract:\e[0m" >&2 + # Tail the last 30 lines of log of YunoHost + # But remove all lines after "ynh_exit_properly" + # Remove the timestamp at the beginning of the line + # Remove "yunohost.hook..." + # Add DEBUG and color it at the beginning of each log line. + echo -e "$(tail --lines=30 "$ynh_log" \ + | sed '1,/ynh_exit_properly/!d' \ + | sed 's/^[[:digit:]: ,-]*//g' \ + | sed 's/ *yunohost.hook.*\]//g' \ + | sed 's/^/\\e[34m\\e[1m[DEBUG]\\e[0m: /g')" >&2 + if type -t ynh_clean_setup > /dev/null; then # Check if the function exist in the app script. ynh_clean_setup # Call the function to do specific cleaning for the app. fi From f71117b401b5d33f703e31287913c6f7fff94076 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Sun, 17 Mar 2019 23:30:57 +0100 Subject: [PATCH 366/721] Support yunohost-api.log --- data/helpers.d/system | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/system b/data/helpers.d/system index 87529c733..3c065e592 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -1,5 +1,14 @@ #!/bin/bash +# Determine whether the scripts is executed from a terminal or the admin JS. +# +# [internal] +ynh_is_term () { + # Return true if $TERM return xterm-XXXcolor + # Which means the script is executed from a terminal, not from the admin JS + [[ "$TERM" =~ "xterm" ]] +} + # Manage a fail of the script # # [internal] @@ -29,7 +38,13 @@ ynh_exit_properly () { # Unset xtrace to not spoil the log set +x - local ynh_log="/var/log/yunohost/yunohost-cli.log" + + if ynh_is_term + then + local ynh_log="/var/log/yunohost/yunohost-cli.log" + else + local ynh_log="/var/log/yunohost/yunohost-api.log" + fi # Wait for the log to be fill with the data until the crash. local timeout=0 while ! tail --lines=20 "$ynh_log" | grep --quiet "ynh_exit_properly" From 18e7be1bc1459cbc6a5f3e234c56214005fee6d4 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 18 Mar 2019 07:57:28 +0100 Subject: [PATCH 367/721] Update psql --- data/helpers.d/psql | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/psql b/data/helpers.d/psql index a9ea5dadc..2212d692a 100644 --- a/data/helpers.d/psql +++ b/data/helpers.d/psql @@ -134,6 +134,7 @@ ynh_psql_dump_db() { # # usage: ynh_psql_create_user user pwd # | arg: user - the user name to create +# | arg: pwd - the password to identify user by # # Requires YunoHost version 3.?.? or higher. ynh_psql_create_user() { From 54e570ea175e8df02b97fffaa7cbd78b8538eb90 Mon Sep 17 00:00:00 2001 From: ppr Date: Sun, 17 Mar 2019 16:48:47 +0000 Subject: [PATCH 368/721] Translated using Weblate (French) Currently translated at 99.8% (503 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 332 +++++++++++++++++++++++++++--------------------- 1 file changed, 187 insertions(+), 145 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 7119039db..b864c5ac7 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -5,29 +5,29 @@ "admin_password_changed": "Le mot de passe d’administration a été modifié", "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_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_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_id_invalid": "Identifiant d’application invalide", "app_incompatible": "L’application {app} est incompatible avec votre version de YunoHost", "app_install_files_invalid": "Fichiers d’installation incorrects", - "app_location_already_used": "L’application « {app} » est déjà installée à cet emplacement ({path})", - "app_location_install_failed": "Impossible d’installer l’application à cet emplacement pour cause de conflit avec l’app « {other_app} » déjà installée sur « {other_path} »", + "app_location_already_used": "L’application '{app}' est déjà installée à cet emplacement ({path})", + "app_location_install_failed": "Impossible d’installer l’application à cet emplacement pour cause de conflit avec l’application '{other_app}' déjà installée sur '{other_path}'", "app_manifest_invalid": "Manifeste d’application incorrect : {error}", "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_package_need_update": "Le paquet de l’application {app} doit être mis à jour pour suivre les changements de YunoHost", + "app_package_need_update": "Le paquet de l’application {app} doit être mis à jour pour être en adéquation avec les changements de YunoHost", "app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost", "app_removed": "{app:s} a été supprimé", - "app_requirements_checking": "Vérification des paquets requis pour {app}...", + "app_requirements_checking": "Vérification des paquets requis pour {app} …", "app_requirements_failed": "Impossible de satisfaire les pré-requis pour {app} : {error}", "app_requirements_unmeet": "Les pré-requis de {app} 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 pris en charge", + "app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté", "app_upgrade_failed": "Impossible de mettre à jour {app:s}", "app_upgraded": "{app:s} a été mis à jour", "appslist_fetched": "La liste d’applications {appslist:s} a été récupérée", @@ -44,35 +44,35 @@ "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_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_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_created": "Sauvegarde terminée", - "backup_creating_archive": "Création de l’archive de sauvegarde...", + "backup_creating_archive": "Création de l’archive de sauvegarde …", "backup_creation_failed": "Impossible de créer la sauvegarde", - "backup_delete_error": "Impossible de supprimer « {path:s} »", + "backup_delete_error": "Impossible de supprimer '{path:s}'", "backup_deleted": "La sauvegarde a é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_extracting_archive": "Extraction de l’archive de sauvegarde …", + "backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu", + "backup_invalid_archive": "Archive de sauvegarde invalide", "backup_nothings_done": "Il n’y a rien à sauvegarder", - "backup_output_directory_forbidden": "Dossier de destination 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_forbidden": "Dossier de destination interdit. Les sauvegardes ne peuvent être créées dans les sous-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", + "backup_running_hooks": "Exécution des scripts de sauvegarde …", + "custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application personnalisée {app:s}", + "custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d’applications personnalisées", "diagnosis_debian_version_error": "Impossible de déterminer la version de Debian : {error}", "diagnosis_kernel_version_error": "Impossible de récupérer la version du noyau : {error}", "diagnosis_monitor_disk_error": "Impossible de superviser les disques : {error}", "diagnosis_monitor_network_error": "Impossible de superviser le réseau : {error}", "diagnosis_monitor_system_error": "Impossible de superviser le système : {error}", "diagnosis_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 »", + "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": "Le domaine a été créé", "domain_creation_failed": "Impossible de créer le domaine", @@ -87,41 +87,41 @@ "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...", + "downloading": "Téléchargement en cours …", "dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été installée", - "dyndns_cron_remove_failed": "Impossible d’enlever la tâche cron pour le domaine DynDNS", + "dyndns_cron_remove_failed": "Impossible de supprimer la tâche cron pour le domaine DynDNS", "dyndns_cron_removed": "La tâche cron pour le domaine DynDNS a été enlevée", "dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS", "dyndns_ip_updated": "Votre adresse IP a été mise à jour pour le domaine DynDNS", - "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...", + "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre un certain 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": "Le domaine DynDNS a été enregistré", "dyndns_registration_failed": "Impossible d’enregistrer le domaine DynDNS : {error:s}", "dyndns_unavailable": "Le domaine {domain:s} est 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} »", + "executing_command": "Exécution de la commande '{command:s}' …", + "executing_script": "Exécution du script '{script:s}' …", + "extracting": "Extraction en cours …", + "field_invalid": "Champ incorrect : '{:s}'", "firewall_reload_failed": "Impossible de recharger le pare-feu", "firewall_reloaded": "Le pare-feu a été rechargé", "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_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 correctement", "hook_list_by_invalid": "La propriété de tri des actions est invalide", - "hook_name_unknown": "Nom de script « {name:s} » inconnu", + "hook_name_unknown": "Nom de l'action '{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 soit dans un conteneur, soit votre noyau ne le prend pas en charge", "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "ldap_initialized": "L’annuaire LDAP a été initialisé", "license_undefined": "indéfinie", - "mail_alias_remove_failed": "Impossible de supprimer l’alias courriel « {mail:s} »", - "mail_domain_unknown": "Le domaine « {domain:s} » du courriel est inconnu", - "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert « {mail:s} »", + "mail_alias_remove_failed": "Impossible de supprimer l’alias courriel '{mail:s}'", + "mail_domain_unknown": "Le domaine '{domain:s}' du courriel est inconnu", + "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "maindomain_change_failed": "Impossible de modifier le domaine principal", "maindomain_changed": "Le domaine principal a été modifié", "monitor_disabled": "La supervision du serveur a été désactivé", @@ -153,7 +153,7 @@ "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é uniquement de caractères alphanumériques et de -_.", + "pattern_backup_archive_name": "Doit être un nom de fichier valide avec un maximum de 30 caractères, et composé uniquement de caractères alphanumériques et de tirets tels que - et _", "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)", "pattern_email": "Doit être une adresse courriel valide (ex. : pseudo@domain.org)", "pattern_firstname": "Doit être un prénom valide", @@ -178,8 +178,8 @@ "restore_failed": "Impossible de restaurer le système", "restore_hook_unavailable": "Le script de restauration « {part:s} » n’est pas disponible sur votre système, et n’est pas non plus dans l’archive", "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...", + "restore_running_app_script": "Exécution du script de restauration de 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": "Le service « {service:s} » a été ajouté", @@ -228,9 +228,9 @@ "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...", + "updating_apt_cache": "Récupération des mises à jour disponibles pour les paquets du système .…", "upgrade_complete": "Mise à jour terminée", - "upgrading_packages": "Mise à jour des paquets...", + "upgrading_packages": "Mise à jour des paquets en cours …", "upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé", "upnp_disabled": "UPnP a été désactivé", "upnp_enabled": "UPnP a été activé", @@ -247,98 +247,98 @@ "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", "yunohost_configured": "YunoHost a été configuré", - "yunohost_installing": "Installation de YunoHost...", + "yunohost_installing": "Installation de YunoHost en cours …", "yunohost_not_installed": "YunoHost n’est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall »", - "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner)", - "certmanager_domain_unknown": "Domaine inconnu {domain:s}", - "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force)", - "certmanager_certificate_fetching_or_enabling_failed": "Il semble que l’activation du nouveau certificat pour {domain:s} a échoué…", - "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain:s} n’est pas fourni par Let’s Encrypt. Impossible de le renouveler automatiquement !", - "certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain:s} est sur le point d’expirer ! Utilisez --force pour contourner", - "certmanager_domain_http_not_working": "Il semble que le domaine {domain:s} n’est pas accessible via HTTP. Veuillez vérifier que vos configuration DNS et nginx sont correctes", - "certmanager_error_no_A_record": "Aucun enregistrement DNS « A » n’a été trouvé pour {domain:s}. De devez faire pointer votre nom de domaine vers votre machine pour être capable d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", - "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS « A » du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez modifié récemment votre enregistrement « A », veuillez attendre sa propagation (quelques vérificateur de propagation sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", - "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), cause : {reason:s}", + "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)", + "certmanager_domain_unknown": "Domaine {domain:s} inconnu", + "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)", + "certmanager_certificate_fetching_or_enabling_failed": "Il semble que l’activation du nouveau certificat pour {domain:s} a échoué …", + "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain:s} n’est pas émis par Let’s Encrypt. Impossible de le renouveler automatiquement !", + "certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain:s} est sur le point d’expirer ! Utilisez --force pour contourner cela", + "certmanager_domain_http_not_working": "Il semble que le domaine {domain:s} n’est pas accessible via HTTP. Veuillez vérifier que vos configuration DNS et Nginx sont correctes", + "certmanager_error_no_A_record": "Aucun enregistrement DNS 'A' n’a été trouvé pour {domain:s}. Vous devez faire pointer votre nom de domaine vers votre machine pour être en mesure d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", + "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS 'A' du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vérificateur de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", + "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}", "certmanager_cert_install_success_selfsigned": "Installation avec succès d’un certificat auto-signé pour le domaine {domain:s} !", "certmanager_cert_install_success": "Installation avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", "certmanager_cert_renew_success": "Renouvellement avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué", "certmanager_no_cert_file": "Impossible de lire le fichier de certificat pour le domaine {domain:s} (fichier : {file:s})", - "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour de défi ACME : le fichier de configuration nginx {filepath:s} est en conflit et doit être retiré au préalable", - "certmanager_hit_rate_limit": "Trop de certificats ont déjà été demandés récemment pour cet ensemble précis de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails", + "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration Nginx {filepath:s} est en conflit et doit être préalablement retiré", + "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", "ldap_init_failed_to_create_admin": "L’initialisation de LDAP n’a pas réussi à créer l’utilisateur admin", "ssowat_persistent_conf_read_error": "Erreur lors de la lecture de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", - "domain_cannot_remove_main": "Impossible de retirer le domaine principal. Définissez un nouveau domaine principal au préalable.", + "domain_cannot_remove_main": "Impossible de supprimer le domaine principal. Commencez par définir un nouveau domaine principal", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", - "mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l’espace disque occupé par la messagerie", + "mailbox_used_space_dovecot_down": "Le service mail Dovecot doit être démarré, si vous souhaitez voir l’espace disque occupé par la messagerie", "domains_available": "Domaines disponibles :", "backup_archive_broken_link": "Impossible d’accéder à l’archive de sauvegarde (lien invalide vers {path:s})", - "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez préalablement exécuter cert-install pour ce domaine.", - "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être déterminé depuis votre serveur YunoHost. Cela peut arriver si vous avez récemment modifié votre enregistrement DNS. Auquel cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)", - "certmanager_http_check_timeout": "Expiration du délai lors de la tentative du serveur de se contacter via HTTP en utilisant son adresse IP publique (domaine {domain:s} avec l’IP {ip:s}). Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.", - "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation/le renouvellement du certificat a été interrompu - veuillez réessayer prochainement.", + "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez d'abord exécuter cert-install.", + "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être résolu depuis votre serveur YunoHost. Cela peut se produire si vous avez récemment modifié votre enregistrement DNS. Si c'est le cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)", + "certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant l'adresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.", + "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé - veuillez réessayer plus tard.", "appslist_retrieve_bad_format": "Le fichier récupéré pour la liste d’applications {appslist:s} n’est pas valide", "domain_hostname_failed": "Échec de la création d’un nouveau nom d’hôte", "yunohost_ca_creation_success": "L’autorité de certification locale a été créée.", "appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s}.", "appslist_url_already_tracked": "Il y a déjà une liste d’applications enregistrée avec l’URL {url:s}.", - "appslist_migrating": "Migration de la liste d’applications {appslist:s}…", - "appslist_could_not_migrate": "Impossible de migrer la liste {appslist:s} ! Impossible d’exploiter l’URL… L’ancienne tâche cron a été conservée dans {bkp_file:s}.", + "appslist_migrating": "Migration de la liste d’applications {appslist:s} …", + "appslist_could_not_migrate": "Impossible de migrer la liste {appslist:s} ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.", "appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit corrompu.", - "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Regardez avec « app changeurl » si c’est disponible.", + "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Regardez avec `app changeurl` si c’est disponible.", "app_change_no_change_url_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL, vous pourriez avoir besoin de la mettre à jour.", - "app_change_url_failed_nginx_reload": "Le redémarrage de nginx a échoué. Voici la sortie de « nginx -t » :\n{nginx_errors:s}", - "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin sont identiques pour {domain:s}{path:s}, aucune action.", - "app_change_url_no_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.", + "app_change_url_failed_nginx_reload": "Le redémarrage de nginx a échoué. Voici la sortie de `nginx -t` :\n{nginx_errors:s}", + "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour (`{domain:s}{path:s}`), rien à faire.", + "app_change_url_no_script": "L’application `{app_name:s}` ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.", "app_change_url_success": "L’URL de l’application {app:s} a été changée en {domain:s}{path:s}", "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante\n{apps:s}", "app_already_up_to_date": "{app:s} est déjà à jour", "invalid_url_format": "Format d’URL non valide", - "global_settings_bad_choice_for_enum": "La valeur du paramètre {setting:s} est incorrecte. Reçu : {received_type:s}; attendu : {expected_type:s}", - "global_settings_bad_type_for_setting": "Le type du paramètre {setting:s} est incorrect. Reçu : {received_type:s}; attendu : {expected_type:s}.", - "global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations, cause : {reason:s}", + "global_settings_bad_choice_for_enum": "La valeur du paramètre {setting:s} est incorrecte. Reçu : {received_type:s} mais attendu : {expected_type:s}", + "global_settings_bad_type_for_setting": "Le type du paramètre {setting:s} est incorrect. Reçu : {received_type:s} mais attendu : {expected_type:s}", + "global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations car : {reason:s}", "global_settings_cant_serialize_setings": "Échec de sérialisation des données de configurations, cause : {reason:s}", - "global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations, cause : {reason:s}", - "global_settings_key_doesnt_exists": "La clef « {settings_key:s} » n’existe pas dans les configurations globales, vous pouvez voir toutes les clefs disponibles en saisissant « yunohost settings list »", + "global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations car : {reason:s}", + "global_settings_key_doesnt_exists": "La clef '{settings_key:s}' n’existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'", "global_settings_reset_success": "Réussite ! Vos configurations précédentes ont été sauvegardées dans {path:s}", "global_settings_setting_example_bool": "Exemple d’option booléenne", "global_settings_setting_example_int": "Exemple d’option de type entier", "global_settings_setting_example_string": "Exemple d’option de type chaîne", "global_settings_setting_example_enum": "Exemple d’option de type énumération", - "global_settings_unknown_type": "Situation inattendue, la configuration {setting:s} semble avoir le type {unknown_type:s} mais ce n’est pas un type pris en charge par le système.", - "global_settings_unknown_setting_from_settings_file": "Clef inconnue dans les configurations : {setting_key:s}, rejet de cette clef et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", + "global_settings_unknown_type": "Situation inattendue, la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n'est pas pris en charge par le système.", + "global_settings_unknown_setting_from_settings_file": "Clef inconnue dans les paramètres : '{setting_key:s}', rejet de cette clef et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", "service_conf_file_kept_back": "Le fichier de configuration « {conf} » devrait être supprimé par le service {service} mais a été conservé.", "backup_abstract_method": "Cette méthode de sauvegarde n’a pas encore été implémentée", - "backup_applying_method_tar": "Création de l’archive tar de la sauvegarde…", - "backup_applying_method_copy": "Copie de tous les fichiers dans la sauvegarde…", - "backup_applying_method_borg": "Envoi de tous les fichiers dans la sauvegarde dans de référentiel borg-backup…", - "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée « {method:s} »…", - "backup_archive_system_part_not_available": "La partie « {part:s} » du système n’est pas disponible dans cette sauvegarde", + "backup_applying_method_tar": "Création de l’archive tar de la sauvegarde …", + "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …", + "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans de référentiel borg-backup …", + "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …", + "backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde", "backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué", - "backup_archive_writing_error": "Impossible d’ajouter les fichiers à la sauvegarde dans l’archive compressée", + "backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source:s}' (nommés dans l'archive : '{dest:s}') à sauvegarder dans l'archive compressée '{archive:s}'", "backup_ask_for_copying_if_needed": "Certains fichiers n’ont pas pu être préparés pour être sauvegardés en utilisant la méthode qui évite temporairement de gaspiller de l’espace sur le système. Pour mener la sauvegarde, {size:s} Mo doivent être temporairement utilisés. Acceptez-vous ?", - "backup_borg_not_implemented": "La méthode de sauvegarde Bord n’est pas encore implémentée", + "backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", - "backup_copying_to_organize_the_archive": "Copie de {size:s} Mio pour organiser l’archive", + "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", "backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire aux opérations futures de restauration", "backup_csv_addition_failed": "Impossible d’ajouter des fichiers à sauvegarder dans le fichier CSV", - "backup_custom_need_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape « need_mount »", - "backup_custom_backup_error": "Échec de la méthode de sauvegarde personnalisée à l’étape « backup »", - "backup_custom_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape « mount »", + "backup_custom_need_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'need_mount'", + "backup_custom_backup_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'backup'", + "backup_custom_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'mount'", "backup_no_uncompress_archive_dir": "Le dossier de l’archive décompressée n’existe pas", "backup_method_tar_finished": "L’archive tar de la sauvegarde a été créée", "backup_method_copy_finished": "La copie de la sauvegarde est terminée", "backup_method_borg_finished": "La sauvegarde dans Borg est terminée", - "backup_method_custom_finished": "La méthode se sauvegarde personnalisée « {method:s} » est terminée", - "backup_system_part_failed": "Impossible de sauvegarder la partie « {part:s} » du système", + "backup_method_custom_finished": "La méthode de sauvegarde personnalisée '{method:s}' est terminée", + "backup_system_part_failed": "Impossible de sauvegarder la partie '{part:s}' du système", "backup_unable_to_organize_files": "Impossible d’organiser les fichiers dans l’archive avec la méthode rapide", "backup_with_no_backup_script_for_app": "L’application {app:s} n’a pas de script de sauvegarde. Ignorer.", "backup_with_no_restore_script_for_app": "L’application {app:s} n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", - "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage, cause : {reason:s}", + "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive…", "restore_mounting_archive": "Montage de l’archive dans « {path:s} »", @@ -367,32 +367,32 @@ "app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications", "ask_path": "Chemin", "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", - "dyndns_domain_not_provided": "Le fournisseur Dyndns {provider:s} ne peut pas fournir le domaine {domain:s}.", - "app_make_default_location_already_used": "Impossible de configurer l’app « {app} » par défaut pour le domaine {domain}, déjà utilisé par l’autre app « {other_app} »", - "app_upgrade_app_name": "Mise à jour de l’application {app}...", - "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque / clef USB.", + "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", + "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine {domain} car déjà utilisé par l'application '{other_app}'", + "app_upgrade_app_name": "Mise à jour de l’application {app} …", + "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives '{path:s}'. Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clef USB.", "migrate_tsig_end": "La migration à hmac-sha512 est terminée", - "migrate_tsig_failed": "La migration du domaine dyndns {domain} à hmac-sha512 a échoué, annulation des modifications. Erreur : {error_code} - {error}", + "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué, annulation des modifications. Erreur : {error_code} - {error}", "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine « {domain} », lancement de la migration vers hmac-sha512 qui est plus sécurisé", - "migrate_tsig_wait": "Attendons 3 minutes pour que le serveur dyndns prenne en compte la nouvelle clef…", - "migrate_tsig_wait_2": "2 minutes…", - "migrate_tsig_wait_3": "1 minute…", - "migrate_tsig_wait_4": "30 secondes…", - "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine dyndns, donc aucune migration n’est nécessaire !", + "migrate_tsig_wait": "Attendre 3 minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …", + "migrate_tsig_wait_2": "2 minutes …", + "migrate_tsig_wait_3": "1 minute …", + "migrate_tsig_wait_4": "30 secondes …", + "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire !", "app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' est obsolète ! Utilisez 'app register-url' en remplacement !", - "migration_description_0001_change_cert_group_to_sslcert": "Change les permissions de groupe des certificats de « metronome » à « ssl-cert »", - "migration_description_0002_migrate_to_tsig_sha256": "Améliore la sécurité de DynDNDS TSIG en utilisant SHA512 au lieu de MD5", + "migration_description_0001_change_cert_group_to_sslcert": "Change les permissions de groupe des certificats de 'metronome' à 'ssl-cert'", + "migration_description_0002_migrate_to_tsig_sha256": "Améliore la sécurité de DynDNS TSIG en utilisant SHA512 au lieu de MD5", "migration_description_0003_migrate_to_stretch": "Mise à niveau du système vers Debian Stretch et YunoHost 3.0", "migration_0003_backward_impossible": "La migration Stretch n’est pas réversible.", "migration_0003_start": "Démarrage de la migration vers Stretch. Les journaux seront disponibles dans {logfile}.", - "migration_0003_patching_sources_list": "Modification de sources.lists…", - "migration_0003_main_upgrade": "Démarrage de la mise à niveau principale…", - "migration_0003_fail2ban_upgrade": "Démarrage de la mise à niveau de fail2ban…", - "migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié d’une manière ou d’une autre. La migration va d’abords le réinitialiser à son état initial… Le fichier précédent sera disponible en tant que {backup_dest}.", - "migration_0003_yunohost_upgrade": "Démarrage de la mise à niveau du paquet YunoHost… La migration terminera, mais la mise à jour réelle aura lieu immédiatement après. Après cette opération terminée, vous pourriez avoir à vous reconnecter à l’administration web.", + "migration_0003_patching_sources_list": "Modification de sources.lists …", + "migration_0003_main_upgrade": "Démarrage de la mise à niveau principale …", + "migration_0003_fail2ban_upgrade": "Démarrage de la mise à niveau de fail2ban …", + "migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié d’une manière ou d’une autre. La migration va d’abords le réinitialiser à son état initial. Le fichier précédent sera disponible en tant que {backup_dest}.", + "migration_0003_yunohost_upgrade": "Démarrage de la mise à niveau du paquet YunoHost. La migration se terminera, mais la mise à jour réelle aura lieu immédiatement après. Après cette opération terminée, vous pourriez avoir à vous reconnecter à l’administration via le panel web.", "migration_0003_not_jessie": "La distribution Debian actuelle n’est pas Jessie !", "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", - "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est ma passé pendant la mise à niveau principale : le système est toujours sur Jessie ?!? Pour investiguer le problème, veuillez regarder {log} 🙁…", + "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est ma passé pendant la mise à niveau principale : le système est toujours sur Jessie ?!? Pour investiguer le problème, veuillez regarder les journaux {log} 🙁…", "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nDe plus, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence !", "migration_0003_problematic_apps_warning": "Veuillez noter que les applications suivantes, éventuellement problématiques, ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées «working ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", @@ -417,49 +417,49 @@ "service_description_ssh": "vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", "service_description_yunohost-api": "permet les interactions entre l’interface web de YunoHost et le système", "service_description_yunohost-firewall": "gère les ports de connexion ouverts et fermés aux services", - "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faîtes.", - "log_corrupted_md_file": "Le fichier yaml de metadata associé aux logs est corrompu : {md_file}", - "log_category_404": "La catégorie de log « {category} » n’existe pas", - "log_link_to_log": "Log complet de cette opération : « {desc} »", - "log_help_to_get_log": "Pour voir le log de cette opération « {desc} », utiliser la commande « yunohost log display {name} »", - "log_link_to_failed_log": "L’opération « {desc} » a échouée ! Pour avoir de l’aide, merci de fournir le log complet de l’opération", - "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge php7, la restauration de vos applications php peut ne pas aboutir (reason: {error:s})", - "log_help_to_get_failed_log": "L’opération « {desc} » a échouée ! Pour avoir de l’aide, merci de partager le log de cette opération en utilisant la commande « yunohost log display {name} --share »", - "log_does_exists": "Il n’existe pas de log de l’opération ayant pour nom « {log} », utiliser « yunohost log list pour voir tous les fichiers de logs disponibles »", + "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", + "log_corrupted_md_file": "Le fichier yaml de metadata associé aux logs est corrompu : {md_file}", + "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", + "log_link_to_log": "Log complet de cette opération : ' {desc} '", + "log_help_to_get_log": "Pour voir le log de cette opération '{desc}', utiliser la commande 'yunohost log display {name}'", + "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de fournir le log complet de l’opération en cliquant ici", + "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge php7, vos applications php pourraient ne pas être restaurées (reason: {error:s})", + "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le log de cette opération en utilisant la commande 'yunohost log display {name} --share'", + "log_does_exists": "Il n’existe pas de log de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de logs disponibles'", "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", - "log_app_addaccess": "Ajouter l’accès à « {} »", - "log_app_removeaccess": "Enlever l’accès à « {} »", - "log_app_clearaccess": "Retirer tous les accès à « {} »", + "log_app_addaccess": "Ajouter l’accès à '{}'", + "log_app_removeaccess": "Enlever l’accès à '{}'", + "log_app_clearaccess": "Retirer tous les accès à '{}'", "log_app_fetchlist": "Ajouter une liste d’application", "log_app_removelist": "Enlever une liste d’application", - "log_app_change_url": "Changer l’url de l’application « {} »", - "log_app_install": "Installer l’application « {} »", - "log_app_remove": "Enlever l’application « {} »", - "log_app_upgrade": "Mettre à jour l’application « {} »", - "log_app_makedefault": "Faire de « {} » l’application par défaut", + "log_app_change_url": "Changer l’url de l’application '{}'", + "log_app_install": "Installer l’application '{}'", + "log_app_remove": "Enlever l’application '{}'", + "log_app_upgrade": "Mettre à jour l’application '{}'", + "log_app_makedefault": "Faire de '{}' l’application par défaut", "log_available_on_yunopaste": "Le log est désormais disponible via {url}", - "log_backup_restore_system": "Restaurer le système depuis une sauvegarde", - "log_backup_restore_app": "Restaurer « {} » depuis une sauvegarde", - "log_remove_on_failed_restore": "Retirer « {} » après la restauration depuis une sauvegarde qui a échouée", - "log_remove_on_failed_install": "Enlever « {} » après une installation échouée", - "log_domain_add": "Ajouter le domaine « {} » dans la configuration du système", - "log_domain_remove": "Enlever le domaine « {} » de la configuration du système", - "log_dyndns_subscribe": "Souscrire au sous-domaine « {} » de Yunohost", - "log_dyndns_update": "Mettre à jour l’adresse ip associée à votre sous-domaine Yunohost « {} »", - "log_letsencrypt_cert_install": "Installer le certificat Let’s encryt sur le domaine « {} »", - "log_selfsigned_cert_install": "Installer le certificat auto-signé sur le domaine « {} »", - "log_letsencrypt_cert_renew": "Renouveler le certificat Let’s encrypt de « {} »", - "log_service_enable": "Activer le service « {} »", - "log_service_regen_conf": "Régénérer la configuration système de « {} »", - "log_user_create": "Ajouter l’utilisateur « {} »", - "log_user_delete": "Enlever l’utilisateur « {} »", - "log_user_update": "Mettre à jour les informations de l’utilisateur « {} »", - "log_tools_maindomain": "Faire de « {} » le domaine principal", - "log_tools_migrations_migrate_forward": "Migrer", + "log_backup_restore_system": "Restaurer le système depuis une archive de sauvegarde", + "log_backup_restore_app": "Restaurer '{}' depuis une sauvegarde", + "log_remove_on_failed_restore": "Retirer '{}' après la restauration depuis une sauvegarde qui a échouée", + "log_remove_on_failed_install": "Enlever '{}' après une installation échouée", + "log_domain_add": "Ajouter le domaine '{}' dans la configuration du système", + "log_domain_remove": "Enlever le domaine '{}' de la configuration du système", + "log_dyndns_subscribe": "Souscrire au sous-domaine YunoHost '{}'", + "log_dyndns_update": "Mettre à jour l’adresse IP associée à votre sous-domaine YunoHost '{}'", + "log_letsencrypt_cert_install": "Installer le certificat Let’s Encrypt sur le domaine '{}'", + "log_selfsigned_cert_install": "Installer le certificat auto-signé sur le domaine '{}'", + "log_letsencrypt_cert_renew": "Renouveler le certificat Let’s Encrypt de '{}'", + "log_service_enable": "Activer le service '{}'", + "log_service_regen_conf": "Régénérer la configuration système de '{}'", + "log_user_create": "Ajouter l’utilisateur '{}'", + "log_user_delete": "Supprimer l’utilisateur '{}'", + "log_user_update": "Mettre à jour les informations de l’utilisateur '{}'", + "log_tools_maindomain": "Faire de '{}' le domaine principal", + "log_tools_migrations_migrate_forward": "Migrer vers", "log_tools_migrations_migrate_backward": "Revenir en arrière", - "log_tools_postinstall": "Faire la post-installation du serveur Yunohost", + "log_tools_postinstall": "Faire la post-installation de votre serveur YunoHost", "log_tools_upgrade": "Mise à jour des paquets Debian", - "log_tools_shutdown": "Eteindre votre serveur", + "log_tools_shutdown": "Éteindre votre serveur", "log_tools_reboot": "Redémarrer votre serveur", "mail_unavailable": "Cette adresse mail est réservée et doit être automatiquement attribuée au tout premier utilisateur", "migration_description_0004_php5_to_php7_pools": "Reconfigurez le pool PHP pour utiliser PHP 7 au lieu de 5", @@ -471,14 +471,56 @@ "service_description_php7.0-fpm": "exécute des applications écrites en PHP avec nginx", "users_available": "Liste des utilisateurs disponibles :", "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase de chiffrement) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", - "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase de chiffrement) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", + "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou d'utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", "migration_0006_disclaimer": "Yunohost s’attend maintenant à ce que les mots de passe admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.", "migration_0006_done": "Votre mot de passe root a été remplacé par celui de votre adminitrateur.", - "password_listed": "Ce mot de passe est l’un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d’un peu plus unique.", + "password_listed": "Ce mot de passe est l'un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d'un peu plus unique.", "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", - "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des caractères majuscules et minuscules", - "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des caractères majuscules, minuscules et spéciaux", - "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des caractères majuscules, minuscules et spéciaux", - "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager sur le mot de passe root !" + "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules", + "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", + "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", + "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager sur le mot de passe root !", + "aborting": "Interruption de la procédure.", + "app_not_upgraded": "Les applications suivantes n'ont pas été mises à jour : {apps}", + "app_start_install": "Installation de l'application {app} …", + "app_start_remove": "Suppression de l'application {app} …", + "app_start_backup": "Collecte des fichiers devant être sauvegardés pour {app} …", + "app_start_restore": "Restauration de l'application {app} …", + "app_upgrade_several_apps": "Les applications suivantes seront mises à jour : {apps}", + "ask_new_domain": "Nouveau domaine", + "ask_new_path": "Nouveau chemin", + "backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés …", + "backup_mount_archive_for_restore": "Préparation de l'archive pour restauration …", + "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{réponses:s}] ", + "confirm_app_install_danger": "AVERTISSEMENT ! Cette application est encore expérimentale (explicitement, elle ne fonctionne pas) et risque de casser votre système ! Vous ne devriez probablement PAS l'installer sans savoir ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{answers:s}] ", + "confirm_app_install_thirdparty": "AVERTISSEMENT ! L'installation d'applications tierces peut compromettre l'intégrité et la sécurité de votre système. Vous ne devriez probablement PAS l'installer si vous ne savez pas ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{réponses:s}] ", + "dpkg_is_broken": "Vous ne pouvez pas faire ça maintenant car dpkg/apt (le gestionnaire de paquets du système) semble avoir laissé des choses non configurées. Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo dpkg --configure -a'.", + "dyndns_could_not_check_available": "Impossible de vérifier si {domain:s} est disponible chez {provider:s}.", + "file_does_not_exist": "Le fichier dont le chemin est {path:s} n'existe pas.", + "global_settings_setting_security_password_admin_strength": "Qualité du mot de passe administrateur", + "global_settings_setting_security_password_user_strength": "Qualité du mot de passe de l'utilisateur", + "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autoriser l'utilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH", + "hook_json_return_error": "Échec de la lecture au retour du script {path:s}. Erreur : {msg:s}. Contenu brut : {raw_content}", + "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuration SSH sera gérée par YunoHost (étape 1, automatique)", + "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuration SSH sera gérée par YunoHost (étape 2, manuelle)", + "migration_0007_cancelled": "YunoHost n'a pas réussi à améliorer la façon dont est gérée votre configuration SSH.", + "migration_0007_cannot_restart": "SSH ne peut pas être redémarré après avoir essayé d'annuler la migration numéro 6.", + "migration_0008_general_disclaimer": "Pour améliorer la sécurité de votre serveur, il est recommandé de laisser YunoHost gérer la configuration SSH. Votre configuration SSH actuelle diffère de la configuration recommandée. Si vous laissez YunoHost la reconfigurer, la façon dont vous vous connectez à votre serveur via SSH changera comme suit :", + "migration_0008_port": " - vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N'hésitez pas à le reconfigurer ;", + "migration_0008_root": " - vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l'utilisateur admin ;", + "migration_0008_dsa": " - la clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;", + "migration_0008_warning": "Si vous comprenez ces avertissements et que vous acceptez de laisser YunoHost remplacer votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également passer la migration, bien que cela ne soit pas recommandé.", + "migration_0008_no_warning": "Aucun risque majeur n'a été identifié concernant l'écrasement de votre configuration SSH - mais nous ne pouvons pas en être absolument sûrs ;) ! Si vous acceptez de laisser YunoHost remplacer votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également passer la migration, bien que cela ne soit pas recommandé.", + "migrations_success": "Migration {number} {name} réussie !", + "pattern_password_app": "Désolé, les mots de passe ne doivent pas contenir les caractères suivants : {forbidden_chars}", + "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", + "service_conf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost.", + "service_reload_failed": "Impossible de recharger le service '{service:s}'.\n\nJournaux récents de ce service : {logs:s}", + "service_reloaded": "Le service '{service:s}' a été rechargé", + "service_restart_failed": "Impossible de redémarrer le service '{service:s}'\n\nJournaux récents de ce service : {logs:s}", + "service_restarted": "Le service '{service:s}' a été redémarré", + "service_reload_or_restart_failed": "Impossible de recharger ou de redémarrer le service '{service:s}'\n\nJournaux récents de ce service : {logs:s}", + "service_reloaded_or_restarted": "Le service '{service:s}' a été rechargé ou redémarré", + "this_action_broke_dpkg": "Cette action a laissé des paquets non configurés par dpkg/apt (les gestionnaires de paquets système). Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo dpkg --configure -a'." } From 3af3c2b77950b4a93c7c806d061372859674657b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 Mar 2019 19:37:52 +0100 Subject: [PATCH 369/721] Directly inject standard css + custom css and js --- data/templates/nginx/plain/yunohost_panel.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_panel.conf.inc b/data/templates/nginx/plain/yunohost_panel.conf.inc index 34afe136d..61f0af613 100644 --- a/data/templates/nginx/plain/yunohost_panel.conf.inc +++ b/data/templates/nginx/plain/yunohost_panel.conf.inc @@ -1,5 +1,5 @@ # Insert YunoHost panel -sub_filter ''; +sub_filter ''; sub_filter_once on; # Apply to other mime types than text/html sub_filter_types application/xhtml+xml; From b99b22bb03bc9b1cd1e53e4491af592bcf31573a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Mar 2019 01:17:45 +0100 Subject: [PATCH 370/721] Propagate file renaming for semantic --- data/templates/nginx/plain/yunohost_panel.conf.inc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/templates/nginx/plain/yunohost_panel.conf.inc b/data/templates/nginx/plain/yunohost_panel.conf.inc index 61f0af613..d159c29f9 100644 --- a/data/templates/nginx/plain/yunohost_panel.conf.inc +++ b/data/templates/nginx/plain/yunohost_panel.conf.inc @@ -1,8 +1,8 @@ -# Insert YunoHost panel -sub_filter ''; +# Insert YunoHost button + portal overlay +sub_filter ''; sub_filter_once on; # Apply to other mime types than text/html sub_filter_types application/xhtml+xml; # Prevent YunoHost panel files from being blocked by specific app rules -location ~ ynhpanel\.(js|json|css) { +location ~ (ynh_portal.js|ynh_overlay.css) { } From 390a755701e1635bc812156e1b9a687a97a0aabd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Mar 2019 13:03:22 +0100 Subject: [PATCH 371/721] Orthotypo stuff --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 9a509048e..62382cfca 100644 --- a/locales/en.json +++ b/locales/en.json @@ -311,7 +311,7 @@ "migration_0005_postgresql_94_not_installed": "Postgresql was not installed on your system. Nothing to do!", "migration_0005_postgresql_96_not_installed": "Postgresql 9.4 has been found to be installed, but not postgresql 9.6!? Something weird might have happened on your system:(…", "migration_0005_not_enough_space": "Not enough space is available in {path} to run the migration right now:(.", - "migration_0006_disclaimer": "Yunohost now expects admin and root passwords to be synchronized. By running this migration, your root password is going to be replaced by the admin password.", + "migration_0006_disclaimer": "YunoHost now expects admin and root passwords to be synchronized. By running this migration, your root password is going to be replaced by the admin password.", "migration_0007_cancelled": "YunoHost has failed to improve the way your SSH conf is managed.", "migration_0007_cannot_restart": "SSH can't be restarted after trying to cancel migration number 6.", "migration_0008_general_disclaimer": "To improve the security of your server, it is recommended to let YunoHost manage the SSH configuration. Your current SSH configuration differs from the recommended configuration. If you let YunoHost reconfigure it, the way you connect to your server through SSH will change in the following way:", From 59ff76591bc894b2f718a0ff5166aa7fc87c5b1e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 Mar 2019 13:52:30 +0100 Subject: [PATCH 372/721] Typo --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 62382cfca..00fe6aa66 100644 --- a/locales/en.json +++ b/locales/en.json @@ -112,7 +112,7 @@ "backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders", "backup_output_directory_not_empty": "The output directory is not empty", "backup_output_directory_required": "You must provide an output directory for the backup", - "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.", + "backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard drive or usb key.", "backup_php5_to_php7_migration_may_fail": "Could not convert your archive to support php7, your php apps may fail to restore (reason: {error:s})", "backup_running_hooks": "Running backup hooks…", "backup_system_part_failed": "Unable to backup the '{part:s}' system part", From 73d42fd51ab25278fde4fc9e1a9804d76cd37a2a Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Tue, 19 Mar 2019 19:30:30 +0100 Subject: [PATCH 373/721] [enh] Avoid to send simultaneously too much emails --- data/templates/postfix/main.cf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index c38896a3f..aea68cd28 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -154,3 +154,7 @@ smtpd_milters = inet:localhost:11332 # Skip email without checking if milter has died milter_default_action = accept + +# Avoid to send simultaneously too much emails +smtp_destination_concurrency_limit = 1 +default_destination_rate_delay = 12s From de7a66ff0e76f21463f22ee151530dbb4f0d7bcb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 01:14:45 +0100 Subject: [PATCH 374/721] Rework system-part of tools_update... --- src/yunohost/tools.py | 51 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a011b1546..482d9305b 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -41,7 +41,7 @@ from moulinette import msettings, msignals, m18n from moulinette.core import init_authenticator from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger -from moulinette.utils.process import check_output +from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_json, write_to_json from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain @@ -469,23 +469,54 @@ def tools_update(ignore_apps=False, ignore_packages=False): # "packages" will list upgradable packages packages = [] if not ignore_packages: - cache = apt.Cache() # Update APT cache + # LC_ALL=C is here to make sure the results are in english + command = "LC_ALL=C apt update" + # TODO : add @is_unit_operation to tools_update so that the + # debug output can be fetched when there's an issue... + callbacks = ( + # stdout goes to debug + lambda l: logger.debug(l.rstrip()), + # stderr goes to warning + # FIXME : filter the damn "CLI interface not stable" from apt >.> + lambda l: logger.warning(l.rstrip()), + ) + logger.info(m18n.n('updating_apt_cache')) - if not cache.update(): + + returncode = call_async_output(command, callbacks, shell=True) + + if returncode != 0: + + # TODO : here, we should run something like a + # `cat /etc/apt/sources.list /etc/apt/sources.list.d/*` + # and append it to the error message to improve debugging + raise YunohostError('update_cache_failed') - cache.open(None) - cache.upgrade(True) + # List upgradable packages + # LC_ALL=C is here to make sure the results are in english + upgradable_raw = check_output("LC_ALL=C apt list --upgradable") - # Add changelogs to the result - for pkg in cache.get_changes(): + # Dirty parsing of the output + upgradable_raw = [l.strip() for l in upgradable_raw.split("\n") if l.strip()] + for line in upgradable_raw: + # Remove stupid warning and verbose messages >.> + if "apt does not have a stable CLI interface" in line or "Listing..." in line: + continue + # line should look like : + # yunohost/stable 3.5.0.2+201903211853 all [upgradable from: 3.4.2.4+201903080053] + line = line.split() + if len(line) != 6: + logger.warning("Failed to parse this line : %s" % ' '.join(line)) + continue packages.append({ - 'name': pkg.name, - 'fullname': pkg.fullname, - 'changelog': pkg.get_changelog() + "name": line[0].split("/")[0], + "new_version": line[1], + "current_version": line[5].strip("]"), }) + logger.debug(m18n.n('done')) # "apps" will list upgradable packages From 51fe6fea277e337732e47239158e68d01a1617b9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 04:01:08 +0100 Subject: [PATCH 375/721] Factorize function to list upgradable packages --- src/yunohost/tools.py | 50 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 482d9305b..93e3dd177 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -495,28 +495,7 @@ def tools_update(ignore_apps=False, ignore_packages=False): raise YunohostError('update_cache_failed') - # List upgradable packages - # LC_ALL=C is here to make sure the results are in english - upgradable_raw = check_output("LC_ALL=C apt list --upgradable") - - # Dirty parsing of the output - upgradable_raw = [l.strip() for l in upgradable_raw.split("\n") if l.strip()] - for line in upgradable_raw: - # Remove stupid warning and verbose messages >.> - if "apt does not have a stable CLI interface" in line or "Listing..." in line: - continue - # line should look like : - # yunohost/stable 3.5.0.2+201903211853 all [upgradable from: 3.4.2.4+201903080053] - line = line.split() - if len(line) != 6: - logger.warning("Failed to parse this line : %s" % ' '.join(line)) - continue - packages.append({ - "name": line[0].split("/")[0], - "new_version": line[1], - "current_version": line[5].strip("]"), - }) - + packages = list(_list_upgradable_apt_packages()) logger.debug(m18n.n('done')) # "apps" will list upgradable packages @@ -545,6 +524,33 @@ def tools_update(ignore_apps=False, ignore_packages=False): return {'packages': packages, 'apps': apps} +# TODO : move this to utils/packages.py ? +def _list_upgradable_apt_packages(): + + # List upgradable packages + # LC_ALL=C is here to make sure the results are in english + upgradable_raw = check_output("LC_ALL=C apt list --upgradable") + + # Dirty parsing of the output + upgradable_raw = [l.strip() for l in upgradable_raw.split("\n") if l.strip()] + for line in upgradable_raw: + # Remove stupid warning and verbose messages >.> + if "apt does not have a stable CLI interface" in line or "Listing..." in line: + continue + # line should look like : + # yunohost/stable 3.5.0.2+201903211853 all [upgradable from: 3.4.2.4+201903080053] + line = line.split() + if len(line) != 6: + logger.warning("Failed to parse this line : %s" % ' '.join(line)) + continue + + yield { + "name": line[0].split("/")[0], + "new_version": line[1], + "current_version": line[5].strip("]"), + } + + @is_unit_operation() def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=False): """ From 096c2a7d7bfa6030206078bc9c19ec2e9305b9c7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 04:02:38 +0100 Subject: [PATCH 376/721] Rework system-part of tools_upgrade... --- src/yunohost/tools.py | 170 +++++++++++++++++++++++++++++------------- 1 file changed, 119 insertions(+), 51 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 93e3dd177..589e49a3f 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -33,6 +33,7 @@ import socket from xmlrpclib import Fault from importlib import import_module from collections import OrderedDict +from datetime import datetime import apt import apt.progress @@ -567,61 +568,127 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal failure = False - # Retrieve interface - is_api = True if msettings.get('interface') == 'api' else False - if not ignore_packages: - apt.apt_pkg.init() - apt.apt_pkg.config.set("DPkg::Options::", "--force-confdef") - apt.apt_pkg.config.set("DPkg::Options::", "--force-confold") - - cache = apt.Cache() - cache.open(None) - cache.upgrade(True) - - # If API call - if is_api: - critical_packages = ("moulinette", "yunohost", - "yunohost-admin", "ssowat", "python") - critical_upgrades = set() - - for pkg in cache.get_changes(): - if pkg.name in critical_packages: - critical_upgrades.add(pkg.name) - # Temporarily keep package ... - pkg.mark_keep() - - # ... and set a hourly cron up to upgrade critical packages - if critical_upgrades: - logger.info(m18n.n('packages_upgrade_critical_later', - packages=', '.join(critical_upgrades))) - with open('/etc/cron.d/yunohost-upgrade', 'w+') as f: - f.write('00 * * * * root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin apt-get install %s -y && rm -f /etc/cron.d/yunohost-upgrade\n' % ' '.join(critical_upgrades)) - - if cache.get_changes(): - logger.info(m18n.n('upgrading_packages')) - - operation_logger.start() - try: - os.environ["DEBIAN_FRONTEND"] = "noninteractive" - # Apply APT changes - # TODO: Logs output for the API - cache.commit(apt.progress.text.AcquireProgress(), - apt.progress.base.InstallProgress()) - except Exception as e: - failure = True - logger.warning('unable to upgrade packages: %s' % str(e)) - logger.error(m18n.n('packages_upgrade_failed')) - operation_logger.error(m18n.n('packages_upgrade_failed')) - else: - logger.info(m18n.n('done')) - operation_logger.success() - finally: - del os.environ["DEBIAN_FRONTEND"] - else: + # Check that there's indeed some packages to upgrade + upgradables = list(_list_upgradable_apt_packages()) + if not upgradables: logger.info(m18n.n('packages_no_upgrade')) + logger.info(m18n.n('upgrading_packages')) + operation_logger.start() + + # Critical packages are packages that we can't just upgrade + # randomly from yunohost itself... upgrading them is likely to + critical_packages = ("moulinette", "yunohost", "yunohost-admin", "ssowat", "python") + + critical_packages_upgradable = [p for p in upgradables if p["name"] in critical_packages] + noncritical_packages_upgradable = [p for p in upgradables if p["name"] not in critical_packages] + + # Prepare dist-upgrade command + dist_upgrade = "DEBIAN_FRONTEND=noninteractive" + dist_upgrade += " APT_LISTCHANGES_FRONTEND=none" + dist_upgrade += " apt-get" + dist_upgrade += " --fix-broken --show-upgraded --assume-yes" + for conf_flag in ["old", "miss", "def"]: + dist_upgrade += ' -o Dpkg::Options::="--force-conf{}"'.format(conf_flag) + dist_upgrade += " dist-upgrade" + + # + # "Regular" packages upgrade + # + if not failure and noncritical_packages_upgradable: + + # TODO : i18n + logger.info("Upgrading 'regular' (non-yunohost-related) packages ...") + + # TODO : factorize this in utils/packages.py ? + # Mark all critical packages as held + for package in critical_packages: + check_output("apt-mark hold %s" % package) + # Doublecheck with apt-mark showhold that packages are indeed held ... + held_packages = check_output("apt-mark showhold").split("\n") + if any(p not in held_packages for p in critical_packages): + failure = True + logger.warning('Unable to hold critical packages ...') + logger.error(m18n.n('packages_upgrade_failed')) + # FIXME : watdo here, should this be an exception or just an + # error + operation_logger.error(m18n.n('packages_upgrade_failed')) + + if not failure: + logger.debug("Running apt command :\n{}".format(dist_upgrade)) + + callbacks = ( + lambda l: logger.info(l.rstrip()), + lambda l: logger.warning(l.rstrip()), + ) + returncode = call_async_output(dist_upgrade, callbacks, shell=True) + if returncode != 0: + failure = True + logger.warning('unable to upgrade packages: %s' % ', '.join(noncritical_packages_upgradable)) + logger.error(m18n.n('packages_upgrade_failed')) + operation_logger.error(m18n.n('packages_upgrade_failed')) + + # + # Critical packages upgrade + # + if not failure and critical_packages_upgradable: + + # TODO : i18n + logger.info("Upgrading 'special' (yunohost-related) packages ...") + + # TODO : factorize this in utils/packages.py ? + # Mark all critical packages as unheld + for package in critical_packages: + check_output("apt-mark unhold %s" % package) + # Doublecheck with apt-mark showhold that packages are indeed unheld ... + unheld_packages = check_output("apt-mark showhold").split("\n") + if any(p in unheld_packages for p in critical_packages): + failure = True + logger.warning('Unable to unhold critical packages ...') + logger.error(m18n.n('packages_upgrade_failed')) + # FIXME : watdo here, should this be an exception or just an + # error + operation_logger.error(m18n.n('packages_upgrade_failed')) + + # + # Here we use a dirty hack to run a command after the current + # "yunohost tools upgrade", because the upgrade of yunohost + # will also trigger other yunohost commands (e.g. "yunohost tools migrations migrate") + # (also the upgrade of the package, if executed from the webadmin, is + # likely to kill/restart the api which is in turn likely to kill this + # command before it ends...) + # + + logfile = "/var/log/yunohost/special_upgrade_%s.log" % datetime.utcnow().strftime("%Y%m%d_%H%M%S") + command = dist_upgrade + " 2>&1 | tee -a {}".format(logfile) + + MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" + wait_until_end_of_yunohost_command = "(while [ -f {} ]; do sleep 2; done)".format(MOULINETTE_LOCK) + + # TODO : i18n + upgrade_completed = "YunoHost package upgrade completed ! Press [enter] to get the command line back" + command = "({} && {}; echo '{}') &".format(wait_until_end_of_yunohost_command, + command, + upgrade_completed) + + logger.debug("Running command :\n{}".format(command)) + os.system(command) + + # TODO / FIXME : return from this function immediately, + # otherwise the apps upgrade might happen and it's gonna be a mess + + # FIXME / open question : what about "permanently" mark yunohost + # as "hold" to avoid accidental deletion of it... + # (so, only unhold it during the upgrade) + + if not failure: + + logger.info(m18n.n('done')) + operation_logger.success() + + if not ignore_apps: try: app_upgrade(auth) @@ -634,6 +701,7 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal logger.success(m18n.n('system_upgraded')) # Return API logs if it is an API call + is_api = True if msettings.get('interface') == 'api' else False if is_api: return {"log": service_log('yunohost-api', number="100").values()[0]} From ad0f65aad713e3f582f437af4d3bc63dc02044ca Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 22 Mar 2019 15:17:39 +0100 Subject: [PATCH 377/721] [enh] Log special upgrade into operation_logger --- src/yunohost/log.py | 20 ++++++++++++++++---- src/yunohost/tools.py | 12 +++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index 857cc3658..7b0401e5b 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -318,14 +318,27 @@ class OperationLogger(object): self.flush() self._register_log() + @property + def md_path(self): + """ + Metadata path file + """ + return os.path.join(self.path, self.name + METADATA_FILE_EXT) + + @property + def log_path(self): + """ + Log path file + """ + return os.path.join(self.path, self.name + LOG_FILE_EXT) + def _register_log(self): """ Register log with a handler connected on log system """ # TODO add a way to not save password on app installation - filename = os.path.join(self.path, self.name + LOG_FILE_EXT) - self.file_handler = FileHandler(filename) + self.file_handler = FileHandler(self.log_path) self.file_handler.formatter = Formatter('%(asctime)s: %(levelname)s - %(message)s') # Listen to the root logger @@ -337,8 +350,7 @@ class OperationLogger(object): Write or rewrite the metadata file with all metadata known """ - filename = os.path.join(self.path, self.name + METADATA_FILE_EXT) - with open(filename, 'w') as outfile: + with open(self.md_path, 'w') as outfile: yaml.safe_dump(self.metadata, outfile, default_flow_style=False) @property diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 589e49a3f..6120ed3a1 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -660,18 +660,20 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal # likely to kill/restart the api which is in turn likely to kill this # command before it ends...) # - - logfile = "/var/log/yunohost/special_upgrade_%s.log" % datetime.utcnow().strftime("%Y%m%d_%H%M%S") + logfile = operation_logger.log_path command = dist_upgrade + " 2>&1 | tee -a {}".format(logfile) MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" wait_until_end_of_yunohost_command = "(while [ -f {} ]; do sleep 2; done)".format(MOULINETTE_LOCK) + update_log_metadata = "sed -i \"s/ended_at: .*$/ended_at: $(date -u +'%Y-%m-%d %H:%M:%S.%N')/\" {}" + update_log_metadata = update_log_metadata.format(operation_logger.md_path) # TODO : i18n upgrade_completed = "YunoHost package upgrade completed ! Press [enter] to get the command line back" - command = "({} && {}; echo '{}') &".format(wait_until_end_of_yunohost_command, - command, - upgrade_completed) + command = "({} && {} && {}; echo '{}') &".format(wait_until_end_of_yunohost_command, + command, + update_log_metadata, + upgrade_completed) logger.debug("Running command :\n{}".format(command)) os.system(command) From ac05ae655721469255a25885c8a451ede31ce6ca Mon Sep 17 00:00:00 2001 From: ljf Date: Fri, 22 Mar 2019 15:32:51 +0100 Subject: [PATCH 378/721] [enh] Support success info with criticla upgrades --- src/yunohost/tools.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 6120ed3a1..9b27b340a 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -665,15 +665,17 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" wait_until_end_of_yunohost_command = "(while [ -f {} ]; do sleep 2; done)".format(MOULINETTE_LOCK) + mark_success = "(echo 'success: true' | tee -a {})".format(operation_logger.md_path) update_log_metadata = "sed -i \"s/ended_at: .*$/ended_at: $(date -u +'%Y-%m-%d %H:%M:%S.%N')/\" {}" update_log_metadata = update_log_metadata.format(operation_logger.md_path) # TODO : i18n upgrade_completed = "YunoHost package upgrade completed ! Press [enter] to get the command line back" - command = "({} && {} && {}; echo '{}') &".format(wait_until_end_of_yunohost_command, - command, - update_log_metadata, - upgrade_completed) + command = "({} && {} && {}; {}; echo '{}') &".format(wait_until_end_of_yunohost_command, + command, + mark_success, + update_log_metadata, + upgrade_completed) logger.debug("Running command :\n{}".format(command)) os.system(command) @@ -685,7 +687,7 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal # as "hold" to avoid accidental deletion of it... # (so, only unhold it during the upgrade) - if not failure: + elif not failure: logger.info(m18n.n('done')) operation_logger.success() From 847cecaf7fe77413af4e675a4631f1db861ceaa9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 16:12:06 +0100 Subject: [PATCH 379/721] [fix] Fix error message for bad choice for setting enums --- locales/en.json | 2 +- src/yunohost/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 00fe6aa66..940201ef4 100644 --- a/locales/en.json +++ b/locales/en.json @@ -199,7 +199,7 @@ "firewall_reloaded": "The firewall has been reloaded", "firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log.", "format_datetime_short": "%m/%d/%Y %I:%M %p", - "global_settings_bad_choice_for_enum": "Bad value for setting {setting:s}, received {received_type:s}, except {expected_type:s}", + "global_settings_bad_choice_for_enum": "Bad choice for setting {setting:s}, received '{choice:s}' but available choices are : {available_choices:s}", "global_settings_bad_type_for_setting": "Bad type for setting {setting:s}, received {received_type:s}, except {expected_type:s}", "global_settings_cant_open_settings": "Failed to open settings file, reason: {reason:s}", "global_settings_cant_serialize_settings": "Failed to serialize settings data, reason: {reason:s}", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 81ea46114..5d2d55ede 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -109,8 +109,8 @@ def settings_set(key, value): elif key_type == "enum": if value not in settings[key]["choices"]: raise YunohostError('global_settings_bad_choice_for_enum', setting=key, - received_type=type(value).__name__, - expected_type=", ".join(settings[key]["choices"])) + choice=value, + available_choices=", ".join(settings[key]["choices"])) else: raise YunohostError('global_settings_unknown_type', setting=key, unknown_type=key_type) From fee79820e097ebefb342bd53a16098661359b196 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 16:15:11 +0100 Subject: [PATCH 380/721] Rename nginx and ssh compatibility setting for consistency --- data/hooks/conf_regen/03-ssh | 4 ++-- data/hooks/conf_regen/15-nginx | 4 ++-- src/yunohost/settings.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index dbf9d69e3..0b58a461c 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -24,8 +24,8 @@ do_pre_regen() { fi # Support different strategy for security configurations - if [[ -n "$(yunohost settings get 'service.ssh.ciphers.compatibility')" ]]; then - ssh_ciphers_compatibility="$(yunohost settings get 'service.ssh.ciphers.compatibility')" + if [[ -n "$(yunohost settings get 'service.ssh.compatibility')" ]]; then + ssh_ciphers_compatibility="$(yunohost settings get 'service.ssh.compatibility')" fi export ssh_keys diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 9a7579eeb..57446c081 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -37,8 +37,8 @@ do_pre_regen() { domain_list=$(sudo yunohost domain list --output-as plain --quiet) # Support different strategy for security configurations - if [[ -n "$(yunohost settings get 'security.ciphers.compatibility')" ]]; then - security_ciphers_compatibility="$(yunohost settings get 'security.ciphers.compatibility')" + if [[ -n "$(yunohost settings get 'security.nginx.compatibility')" ]]; then + security_ciphers_compatibility="$(yunohost settings get 'security.nginx.compatibility')" fi export security_ciphers_compatibility diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 916e8b8c3..6277c8283 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -40,9 +40,9 @@ DEFAULTS = OrderedDict([ ("security.password.admin.strength", {"type": "int", "default": 1}), ("security.password.user.strength", {"type": "int", "default": 1}), ("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", "default": False}), - ("service.ssh.ciphers.compatibility", {"type": "enum", "default": "modern", + ("security.ssh.compatibility", {"type": "enum", "default": "modern", "choices": ["intermediate", "modern"]}), - ("security.ciphers.compatibility", {"type": "enum", "default": "intermediate", + ("security.nginx.compatibility", {"type": "enum", "default": "intermediate", "choices": ["intermediate", "modern"]}), ]) @@ -283,12 +283,12 @@ def trigger_post_change_hook(setting_name, old_value, new_value): # # =========================================== -@post_change_hook("security.ciphers.compatibility") +@post_change_hook("security.nginx.compatibility") def reconfigure_nginx(setting_name, old_value, new_value): if old_value != new_value: service_regen_conf(names=['nginx']) -@post_change_hook("service.ssh.ciphers.compatibility") +@post_change_hook("security.ssh.compatibility") def reconfigure_ssh(setting_name, old_value, new_value): if old_value != new_value: service_regen_conf(names=['ssh']) From 2defd6ffa82368a6714b8850a1a1a960d345ac1e Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Fri, 22 Mar 2019 16:20:35 +0100 Subject: [PATCH 381/721] [fix] Keep user info in json format Previously it was ynhpanel.json --- data/templates/nginx/plain/yunohost_panel.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_panel.conf.inc b/data/templates/nginx/plain/yunohost_panel.conf.inc index d159c29f9..b36d79119 100644 --- a/data/templates/nginx/plain/yunohost_panel.conf.inc +++ b/data/templates/nginx/plain/yunohost_panel.conf.inc @@ -4,5 +4,5 @@ sub_filter_once on; # Apply to other mime types than text/html sub_filter_types application/xhtml+xml; # Prevent YunoHost panel files from being blocked by specific app rules -location ~ (ynh_portal.js|ynh_overlay.css) { +location ~ (ynh_portal.js|ynh_overlay.css|userinfo.json) { } From c8d8e0e272b89e660f8f6ad860bb748642c4709a Mon Sep 17 00:00:00 2001 From: "ljf (zamentur)" Date: Fri, 22 Mar 2019 16:30:02 +0100 Subject: [PATCH 382/721] [fix] Avoid potential conflict with some apps --- data/templates/nginx/plain/yunohost_panel.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/plain/yunohost_panel.conf.inc b/data/templates/nginx/plain/yunohost_panel.conf.inc index b36d79119..1c5a2d656 100644 --- a/data/templates/nginx/plain/yunohost_panel.conf.inc +++ b/data/templates/nginx/plain/yunohost_panel.conf.inc @@ -4,5 +4,5 @@ sub_filter_once on; # Apply to other mime types than text/html sub_filter_types application/xhtml+xml; # Prevent YunoHost panel files from being blocked by specific app rules -location ~ (ynh_portal.js|ynh_overlay.css|userinfo.json) { +location ~ (ynh_portal.js|ynh_overlay.css|ynh_userinfo.json) { } From dcff10f6f84d03c5f15cfa29cd4698cdcb365bd3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 16:31:45 +0100 Subject: [PATCH 383/721] Simplify setting usage in regenconf hooks --- data/hooks/conf_regen/03-ssh | 5 +---- data/hooks/conf_regen/15-nginx | 6 +----- data/templates/nginx/plain/yunohost_admin.conf | 2 +- data/templates/nginx/server.tpl.conf | 2 +- data/templates/ssh/sshd_config | 2 +- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index 0b58a461c..265c2f746 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -24,13 +24,10 @@ do_pre_regen() { fi # Support different strategy for security configurations - if [[ -n "$(yunohost settings get 'service.ssh.compatibility')" ]]; then - ssh_ciphers_compatibility="$(yunohost settings get 'service.ssh.compatibility')" - fi + export compatibility="$(yunohost settings get 'service.ssh.compatibility')" export ssh_keys export ipv6_enabled - export ssh_ciphers_compatibility ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config" } diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx index 57446c081..60e719743 100755 --- a/data/hooks/conf_regen/15-nginx +++ b/data/hooks/conf_regen/15-nginx @@ -37,11 +37,7 @@ do_pre_regen() { domain_list=$(sudo yunohost domain list --output-as plain --quiet) # Support different strategy for security configurations - if [[ -n "$(yunohost settings get 'security.nginx.compatibility')" ]]; then - security_ciphers_compatibility="$(yunohost settings get 'security.nginx.compatibility')" - fi - - export security_ciphers_compatibility + export compatibility="$(yunohost settings get 'security.nginx.compatibility')" # add domain conf files for domain in $domain_list; do diff --git a/data/templates/nginx/plain/yunohost_admin.conf b/data/templates/nginx/plain/yunohost_admin.conf index 71ad22545..c785a63c4 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf +++ b/data/templates/nginx/plain/yunohost_admin.conf @@ -20,7 +20,7 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; - {% if security_ciphers_compatibility == "modern" %} + {% if compatibility == "modern" %} # Ciphers with modern compatibility # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern # Uncomment the following to use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index b25e38faa..26bc78b39 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -29,7 +29,7 @@ server { ssl_session_timeout 5m; ssl_session_cache shared:SSL:50m; - {% if security_ciphers_compatibility == "modern" %} + {% if compatibility == "modern" %} # Ciphers with modern compatibility # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern # The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...) diff --git a/data/templates/ssh/sshd_config b/data/templates/ssh/sshd_config index f27ca3ebe..8dc0e8dfc 100644 --- a/data/templates/ssh/sshd_config +++ b/data/templates/ssh/sshd_config @@ -15,7 +15,7 @@ HostKey {{ key }}{% endfor %} # https://infosec.mozilla.org/guidelines/openssh # ############################################## -{% if ssh_ciphers_compatibility == "intermediate" %} +{% if compatibility == "intermediate" %} KexAlgorithms diffie-hellman-group-exchange-sha256 Ciphers aes256-ctr,aes192-ctr,aes128-ctr MACs hmac-sha2-512,hmac-sha2-256 From 219dd2590c7ae876ec194fb4522e86d48a618b0a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 17:12:38 +0100 Subject: [PATCH 384/721] Update settings descriptions --- locales/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index e56b8e304..af3360b8a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -210,12 +210,12 @@ "global_settings_setting_example_enum": "Example enum option", "global_settings_setting_example_int": "Example int option", "global_settings_setting_example_string": "Example string option", - "global_settings_setting_security_ciphers_compatibility": "Admin ciphers compatibility strategy for the web", + "global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server nginx. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_security_password_user_strength": "User password strength", + "global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)", "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", - "global_settings_setting_service_ssh_ciphers_compatibility": "Admin ciphers compatibility strategy for SSH", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", From fefa44e0621fa6ee28515079df0755dc9e7d7e09 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 17:14:48 +0100 Subject: [PATCH 385/721] Typo in previous commits --- data/hooks/conf_regen/03-ssh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/03-ssh b/data/hooks/conf_regen/03-ssh index 265c2f746..54b7c55b7 100755 --- a/data/hooks/conf_regen/03-ssh +++ b/data/hooks/conf_regen/03-ssh @@ -24,7 +24,7 @@ do_pre_regen() { fi # Support different strategy for security configurations - export compatibility="$(yunohost settings get 'service.ssh.compatibility')" + export compatibility="$(yunohost settings get 'security.ssh.compatibility')" export ssh_keys export ipv6_enabled From e2e9a7a0339ae269a239156972595d6ff590cebe Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 17:47:32 +0100 Subject: [PATCH 386/721] Remove all deprecated lists, not just 'yunohost' --- .../0009_migrate_to_apps_json.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/yunohost/data_migrations/0009_migrate_to_apps_json.py b/src/yunohost/data_migrations/0009_migrate_to_apps_json.py index 364497b46..b5ae1130b 100644 --- a/src/yunohost/data_migrations/0009_migrate_to_apps_json.py +++ b/src/yunohost/data_migrations/0009_migrate_to_apps_json.py @@ -1,5 +1,5 @@ from moulinette.utils.log import getActionLogger -from yunohost.app import app_fetchlist, app_removelist +from yunohost.app import app_fetchlist, app_removelist, _read_appslist_list from yunohost.tools import Migration logger = getActionLogger('yunohost.migration') @@ -10,12 +10,21 @@ class MyMigration(Migration): def migrate(self): - # Remove official.json list - app_removelist(name="yunohost") + # Remove all the deprecated lists + lists_to_remove = [ + "https://app.yunohost.org/official.json", + "https://app.yunohost.org/community.json", + "https://labriqueinter.net/apps/labriqueinternet.json" + ] + + appslists = _read_appslist_list() + for appslist, infos in appslists.items(): + if infos["url"] in lists_to_remove: + app_removelist(name=appslist) # Replace by apps.json list app_fetchlist(name="yunohost", - url="https://app.yunohost.org/apps.json") + url="https://app.yunohost.org/apps.json") def backward(self): From 93c978ba422b26971180a4277a0b69e82848ee78 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 17:58:46 +0100 Subject: [PATCH 387/721] Backup / restore original appslist to handle backward case properly --- .../0009_migrate_to_apps_json.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/yunohost/data_migrations/0009_migrate_to_apps_json.py b/src/yunohost/data_migrations/0009_migrate_to_apps_json.py index b5ae1130b..8c81746d9 100644 --- a/src/yunohost/data_migrations/0009_migrate_to_apps_json.py +++ b/src/yunohost/data_migrations/0009_migrate_to_apps_json.py @@ -1,15 +1,25 @@ +import os + from moulinette.utils.log import getActionLogger -from yunohost.app import app_fetchlist, app_removelist, _read_appslist_list +from yunohost.app import app_fetchlist, app_removelist, _read_appslist_list, APPSLISTS_JSON from yunohost.tools import Migration logger = getActionLogger('yunohost.migration') +BASE_CONF_PATH = '/home/yunohost.conf' +BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup') +APPSLISTS_BACKUP = os.path.join(BACKUP_CONF_DIR, "appslist_before_migration_0009.json") + + class MyMigration(Migration): "Migrate from official.json to apps.json" def migrate(self): + # Backup current app list json + os.system("cp %s %s") % (APPSLISTS_JSON, APPSLISTS_BACKUP) + # Remove all the deprecated lists lists_to_remove = [ "https://app.yunohost.org/official.json", @@ -28,9 +38,5 @@ class MyMigration(Migration): def backward(self): - # Remove apps.json list - app_removelist(name="yunohost") - - # Replace by official.json list - app_fetchlist(name="yunohost", - url="https://app.yunohost.org/official.json") + if os.path.exists(APPSLISTS_BACKUP): + os.system("cp %s %s") % (APPSLISTS_BACKUP, APPSLISTS_JSON) From d6b6fff7064c06ec505de5c5aaf572b776223ce9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 18:00:46 +0100 Subject: [PATCH 388/721] Add description for migration 0009 --- locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/locales/en.json b/locales/en.json index 1157e0a54..2a2db5340 100644 --- a/locales/en.json +++ b/locales/en.json @@ -294,6 +294,7 @@ "migration_description_0006_sync_admin_and_root_passwords": "Synchronize admin and root passwords", "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)", "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)", + "migration_description_0009_migrate_to_apps_json": "Remove deprecated appslists and use the new unified 'apps.json' list instead.", "migration_0003_backward_impossible": "The stretch migration cannot be reverted.", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", From d0df02837347def4b8e9b66c92a2a74937083ff3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 18:06:39 +0100 Subject: [PATCH 389/721] Woopsi typo --- src/yunohost/data_migrations/0009_migrate_to_apps_json.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/data_migrations/0009_migrate_to_apps_json.py b/src/yunohost/data_migrations/0009_migrate_to_apps_json.py index 8c81746d9..4f4baa5bd 100644 --- a/src/yunohost/data_migrations/0009_migrate_to_apps_json.py +++ b/src/yunohost/data_migrations/0009_migrate_to_apps_json.py @@ -18,7 +18,7 @@ class MyMigration(Migration): def migrate(self): # Backup current app list json - os.system("cp %s %s") % (APPSLISTS_JSON, APPSLISTS_BACKUP) + os.system("cp %s %s" % (APPSLISTS_JSON, APPSLISTS_BACKUP)) # Remove all the deprecated lists lists_to_remove = [ @@ -39,4 +39,4 @@ class MyMigration(Migration): def backward(self): if os.path.exists(APPSLISTS_BACKUP): - os.system("cp %s %s") % (APPSLISTS_BACKUP, APPSLISTS_JSON) + os.system("cp %s %s" % (APPSLISTS_BACKUP, APPSLISTS_JSON)) From 1268872ecfec60f2688f9eba2bc92a7f4d632646 Mon Sep 17 00:00:00 2001 From: Romuald Date: Sat, 23 Mar 2019 11:58:07 +0100 Subject: [PATCH 390/721] Fix filename in error message --- locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 940201ef4..41c68fd16 100644 --- a/locales/en.json +++ b/locales/en.json @@ -212,7 +212,7 @@ "global_settings_setting_example_string": "Example string option", "global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_security_password_user_strength": "User password strength", - "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json", + "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).", From a86c379233e4305cda886d89b9aa254876a44c25 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Sat, 23 Mar 2019 16:46:40 +0100 Subject: [PATCH 391/721] fix syntax in nodejs helper --- data/helpers.d/nodejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 61a1414ef..1730990e7 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -99,7 +99,7 @@ ynh_install_nodejs () { # Install the requested version of nodejs uname=$(uname -m) - if [[ $uname =~ aarch64 || $uname =~ arm64]] + if [[ $uname =~ aarch64 || $uname =~ arm64 ]] then n $nodejs_version --arch=arm64 else From dd87b9192aefde56175f54aa5aa20cdf98baa9b8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 23 Mar 2019 16:50:44 +0100 Subject: [PATCH 392/721] Improve naming / semantic / add comments --- src/yunohost/domain.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index e604b54f0..a7141e0b8 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -408,21 +408,38 @@ def _build_dns_conf(domain, ttl=3600): ] # Official record - res = { + records = { "basic": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in basic], "xmpp": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in xmpp], "mail": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in mail], "extra": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in extra], } - # Custom record - hookres = hook_callback('custom_dns_rules', args=[domain]) - for n, val in hookres.items() : - res[n] = [] - for v in [v['stdreturn'] for p, v in val.items() if v and v['stdreturn']]: - res[n].extend(v) + # Custom records + hook_results = hook_callback('custom_dns_rules', args=[domain]) + for hook_name, results in hook_results.items(): + # + # There can be multiple results per hook name, so results look like + # {'/some/path/to/hook1': + # { 'state': 'succeed', + # 'stdreturn': [{'type': 'SRV', + # 'name': 'stuff.foo.bar.', + # 'value': 'yoloswag', + # 'ttl': 3600}] + # }, + # '/some/path/to/hook2': + # { ... }, + # [...] + # + # Loop over the sub-results + custom_records = [v['stdreturn'] for v in results.values() + if v and v['stdreturn']] - return res + records[hook_name] = [] + for record_list in custom_records: + records[hook_name].extend(record_list) + + return records def _get_DKIM(domain): From cd9fcd265d99e52d4da02f5546483d802c074c98 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Sat, 23 Mar 2019 16:54:47 +0100 Subject: [PATCH 393/721] fix conflict --- data/helpers.d/nodejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 1730990e7..098ed4410 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -99,7 +99,7 @@ ynh_install_nodejs () { # Install the requested version of nodejs uname=$(uname -m) - if [[ $uname =~ aarch64 || $uname =~ arm64 ]] + if [[ $uname =~ aarch64 || $uname =~ arm64 ]] then n $nodejs_version --arch=arm64 else From 5ac5cc93107efea4a5855afb686d4f4640d244bf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 23 Mar 2019 17:31:56 +0100 Subject: [PATCH 394/721] Check format of data retrieved from hook stdreturn --- src/yunohost/domain.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index a7141e0b8..70a4ef5c9 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -437,6 +437,15 @@ def _build_dns_conf(domain, ttl=3600): records[hook_name] = [] for record_list in custom_records: + # Check that record_list is indeed a list of dict + # with the required keys + if not isinstance(record_list, list) \ + or any(not isinstance(record, dict) for record in record_list) \ + or any(key not in record for record in record_list for key in ["name", "ttl", "type", "value"]): + # Display an error, mainly for app packagers trying to implement a hook + logger.warning("Ignored custom record from hook '%s' because the data is not a *list* of dict with keys name, ttl, type and value. Raw data : %s" % (hook_name, record_list)) + continue + records[hook_name].extend(record_list) return records From fad3edf66a21ef56b43b5e314745b167bb683175 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 25 Mar 2019 16:50:49 +0100 Subject: [PATCH 395/721] [fix] SSH login breaks if password is longer than 127 chars... --- locales/en.json | 1 + src/yunohost/tools.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/locales/en.json b/locales/en.json index 18ab84be1..694df0707 100644 --- a/locales/en.json +++ b/locales/en.json @@ -4,6 +4,7 @@ "admin_password": "Administration password", "admin_password_change_failed": "Unable to change password", "admin_password_changed": "The administration password has been changed", + "admin_password_too_long": "Please choose a password shorter than 127 characters", "app_already_installed": "{app:s} is already installed", "app_already_installed_cant_change_url": "This app is already installed. The url cannot be changed just by this function. Look into `app changeurl` if it's available.", "app_already_up_to_date": "{app:s} is already up to date", diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index a011b1546..42114c7e9 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -132,6 +132,11 @@ def tools_adminpw(auth, new_password, check_strength=True): if check_strength: assert_password_is_strong_enough("admin", new_password) + # UNIX seems to not like password longer than 127 chars ... + # e.g. SSH login gets broken (or even 'su admin' when entering the password) + if len(new_password) >= 127: + raise YunohostError('admin_password_too_long') + new_hash = _hash_user_password(new_password) try: From 84c66990c34d3bb28c3eef819fbfcd2bd470e44c Mon Sep 17 00:00:00 2001 From: Gabriel Corona Date: Sat, 16 Mar 2019 23:04:33 +0100 Subject: [PATCH 396/721] Avoid having to shell-escape arguments in ynh_handle_getopts_args Fixes $ ` and \ injections. --- data/helpers.d/getopts | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index 694543e1d..894c9395f 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -152,19 +152,8 @@ ynh_handle_getopts_args () { # If there's already another value for this option, add a ; before adding the new value eval ${option_var}+="\;" fi - # Escape double quote to prevent any interpretation during the eval - all_args[$i]="${all_args[$i]//\"/\\\"}" - # Escape $ as well to prevent the string following it to be seen as a variable. - all_args[$i]="${all_args[$i]//$/\\\$}" - # For the record. - # We're using eval here to get the content of the variable stored itself as simple text in $option_var... - # Other ways to get that content would be to use either ${!option_var} or declare -g ${option_var} - # But... ${!option_var} can't be used as left part of an assignation. - # declare -g ${option_var} will create a local variable (despite -g !) and will not be available for the helper itself. - # So... Stop fucking arguing each time that eval is evil... Go find an other working solution if you can find one! - - eval ${option_var}+=\"${all_args[$i]}\" + eval ${option_var}+='"${all_args[$i]}"' shift_value=$(( shift_value + 1 )) fi done @@ -202,14 +191,7 @@ ynh_handle_getopts_args () { # The variable name will be stored in 'option_var' local option_var="${args_array[$option_flag]%=}" - # Escape double quote to prevent any interpretation during the eval - arguments[$i]="${arguments[$i]//\"/\\\"}" - # Escape $ as well to prevent the string following it to be seen as a variable. - arguments[$i]="${arguments[$i]//$/\\\$}" - - # Store each value given as argument in the corresponding variable - # The values will be stored in the same order than $args_array - eval ${option_var}+=\"${arguments[$i]}\" + eval ${option_var}+='"${arguments[$i]}"' done unset legacy_args else From 2f0dd973b8686de62f92b1aefa91a6d68ebb6100 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 01:14:45 +0100 Subject: [PATCH 397/721] Rework system-part of tools_update... --- src/yunohost/tools.py | 51 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 42114c7e9..92c68eca9 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -41,7 +41,7 @@ from moulinette import msettings, msignals, m18n from moulinette.core import init_authenticator from yunohost.utils.error import YunohostError from moulinette.utils.log import getActionLogger -from moulinette.utils.process import check_output +from moulinette.utils.process import check_output, call_async_output from moulinette.utils.filesystem import read_json, write_to_json from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain @@ -474,23 +474,54 @@ def tools_update(ignore_apps=False, ignore_packages=False): # "packages" will list upgradable packages packages = [] if not ignore_packages: - cache = apt.Cache() # Update APT cache + # LC_ALL=C is here to make sure the results are in english + command = "LC_ALL=C apt update" + # TODO : add @is_unit_operation to tools_update so that the + # debug output can be fetched when there's an issue... + callbacks = ( + # stdout goes to debug + lambda l: logger.debug(l.rstrip()), + # stderr goes to warning + # FIXME : filter the damn "CLI interface not stable" from apt >.> + lambda l: logger.warning(l.rstrip()), + ) + logger.info(m18n.n('updating_apt_cache')) - if not cache.update(): + + returncode = call_async_output(command, callbacks, shell=True) + + if returncode != 0: + + # TODO : here, we should run something like a + # `cat /etc/apt/sources.list /etc/apt/sources.list.d/*` + # and append it to the error message to improve debugging + raise YunohostError('update_cache_failed') - cache.open(None) - cache.upgrade(True) + # List upgradable packages + # LC_ALL=C is here to make sure the results are in english + upgradable_raw = check_output("LC_ALL=C apt list --upgradable") - # Add changelogs to the result - for pkg in cache.get_changes(): + # Dirty parsing of the output + upgradable_raw = [l.strip() for l in upgradable_raw.split("\n") if l.strip()] + for line in upgradable_raw: + # Remove stupid warning and verbose messages >.> + if "apt does not have a stable CLI interface" in line or "Listing..." in line: + continue + # line should look like : + # yunohost/stable 3.5.0.2+201903211853 all [upgradable from: 3.4.2.4+201903080053] + line = line.split() + if len(line) != 6: + logger.warning("Failed to parse this line : %s" % ' '.join(line)) + continue packages.append({ - 'name': pkg.name, - 'fullname': pkg.fullname, - 'changelog': pkg.get_changelog() + "name": line[0].split("/")[0], + "new_version": line[1], + "current_version": line[5].strip("]"), }) + logger.debug(m18n.n('done')) # "apps" will list upgradable packages From 2f034bb7c9b6d62943a9ea1897b3000b5c33f193 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 22 Mar 2019 04:01:08 +0100 Subject: [PATCH 398/721] Factorize function to list upgradable packages --- src/yunohost/tools.py | 50 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 92c68eca9..154ad086a 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -500,28 +500,7 @@ def tools_update(ignore_apps=False, ignore_packages=False): raise YunohostError('update_cache_failed') - # List upgradable packages - # LC_ALL=C is here to make sure the results are in english - upgradable_raw = check_output("LC_ALL=C apt list --upgradable") - - # Dirty parsing of the output - upgradable_raw = [l.strip() for l in upgradable_raw.split("\n") if l.strip()] - for line in upgradable_raw: - # Remove stupid warning and verbose messages >.> - if "apt does not have a stable CLI interface" in line or "Listing..." in line: - continue - # line should look like : - # yunohost/stable 3.5.0.2+201903211853 all [upgradable from: 3.4.2.4+201903080053] - line = line.split() - if len(line) != 6: - logger.warning("Failed to parse this line : %s" % ' '.join(line)) - continue - packages.append({ - "name": line[0].split("/")[0], - "new_version": line[1], - "current_version": line[5].strip("]"), - }) - + packages = list(_list_upgradable_apt_packages()) logger.debug(m18n.n('done')) # "apps" will list upgradable packages @@ -550,6 +529,33 @@ def tools_update(ignore_apps=False, ignore_packages=False): return {'packages': packages, 'apps': apps} +# TODO : move this to utils/packages.py ? +def _list_upgradable_apt_packages(): + + # List upgradable packages + # LC_ALL=C is here to make sure the results are in english + upgradable_raw = check_output("LC_ALL=C apt list --upgradable") + + # Dirty parsing of the output + upgradable_raw = [l.strip() for l in upgradable_raw.split("\n") if l.strip()] + for line in upgradable_raw: + # Remove stupid warning and verbose messages >.> + if "apt does not have a stable CLI interface" in line or "Listing..." in line: + continue + # line should look like : + # yunohost/stable 3.5.0.2+201903211853 all [upgradable from: 3.4.2.4+201903080053] + line = line.split() + if len(line) != 6: + logger.warning("Failed to parse this line : %s" % ' '.join(line)) + continue + + yield { + "name": line[0].split("/")[0], + "new_version": line[1], + "current_version": line[5].strip("]"), + } + + @is_unit_operation() def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=False): """ From f35eb1785437d9f7b1fd3c66edeb83868dc26418 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 26 Mar 2019 18:49:32 +0100 Subject: [PATCH 399/721] Mark YunoHost as essential to avoid removing it inadvertenly --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 685c194ba..a6fd95154 100644 --- a/debian/control +++ b/debian/control @@ -1,6 +1,7 @@ Source: yunohost Section: utils Priority: extra +Essential: yes Maintainer: YunoHost Contributors Build-Depends: debhelper (>=9), dh-systemd, dh-python, python-all (>= 2.7) Standards-Version: 3.9.6 From e298838949ce3b2ab924a303dff3cbeffc9b39f3 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 26 Mar 2019 19:45:16 +0100 Subject: [PATCH 400/721] Filter boring apt warnings + report an error if there was real warnings --- locales/en.json | 1 + src/yunohost/tools.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 694df0707..da27c7cb0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -483,6 +483,7 @@ "unlimit": "No quota", "unrestore_app": "App '{app:s}' will not be restored", "update_cache_failed": "Unable to update APT cache", + "update_cache_warning": "Some errors happened while updating APT cache", "updating_apt_cache": "Fetching available upgrades for system packages…", "upgrade_complete": "Upgrade complete", "upgrading_packages": "Upgrading packages…", diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 154ad086a..f9d86ad09 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -480,12 +480,21 @@ def tools_update(ignore_apps=False, ignore_packages=False): command = "LC_ALL=C apt update" # TODO : add @is_unit_operation to tools_update so that the # debug output can be fetched when there's an issue... + + # Filter boring message about "apt not having a stable CLI interface" + # Also keep track of wether or not we encountered a warning... + warnings = [] + def is_legit_warning(m): + legit_warning = m.rstrip() and "apt does not have a stable CLI interface" not in m.rstrip() + if legit_warning: + warnings.append(m) + return legit_warning + callbacks = ( # stdout goes to debug lambda l: logger.debug(l.rstrip()), - # stderr goes to warning - # FIXME : filter the damn "CLI interface not stable" from apt >.> - lambda l: logger.warning(l.rstrip()), + # stderr goes to warning except for the boring apt messages + lambda l: logger.warning(l.rstrip()) if is_legit_warning(l) else logger.debug(l.rstrip()) ) logger.info(m18n.n('updating_apt_cache')) @@ -499,6 +508,8 @@ def tools_update(ignore_apps=False, ignore_packages=False): # and append it to the error message to improve debugging raise YunohostError('update_cache_failed') + elif warnings: + logger.error(m18n.n('update_cache_warning')) packages = list(_list_upgradable_apt_packages()) logger.debug(m18n.n('done')) From 0bd781be4735169685473f7e3969c6a45f6c0b3e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 27 Mar 2019 15:11:11 +0100 Subject: [PATCH 401/721] [fix] Small issue with optional arguments... c.f. issue 1261 --- src/yunohost/app.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index f21352fc2..8f2de0caa 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2257,13 +2257,17 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None): elif arg_default is not None: arg_value = arg_default - # Validate argument value - if (arg_value is None or arg_value == '') \ - and not arg.get('optional', False): - raise YunohostError('app_argument_required', name=arg_name) - elif arg_value is None: - args_dict[arg_name] = '' - continue + # If the value is empty (none or '') + # then check if arg is optional or not + if arg_value is None or arg_value == '': + if arg.get("optional", False): + # Argument is optional, keep an empty value + # and that's all for this arg ! + args_dict[arg_name] = '' + continue + else: + # The argument is required ! + raise YunohostError('app_argument_required', name=arg_name) # Validate argument choice if arg_choices and arg_value not in arg_choices: From 162eeb7e06e04ec32ed66331a184c9527fa68e82 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 27 Mar 2019 18:08:46 +0100 Subject: [PATCH 402/721] Dump sources list in error message to help debugging --- locales/en.json | 4 ++-- src/yunohost/tools.py | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/locales/en.json b/locales/en.json index da27c7cb0..64f2e6766 100644 --- a/locales/en.json +++ b/locales/en.json @@ -482,8 +482,8 @@ "unit_unknown": "Unknown unit '{unit:s}'", "unlimit": "No quota", "unrestore_app": "App '{app:s}' will not be restored", - "update_cache_failed": "Unable to update APT cache", - "update_cache_warning": "Some errors happened while updating APT cache", + "update_apt_cache_failed": "Unable to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines which might help to identify problematic lines : \n{sourceslist}", + "update_apt_cache_warning": "Some errors happened while updating the cache of APT (Debian's package manager). Here is a dump of the sources.list lines which might help to identify problematic lines : \n{sourceslist}", "updating_apt_cache": "Fetching available upgrades for system packages…", "upgrade_complete": "Upgrade complete", "upgrading_packages": "Upgrading packages…", diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index f9d86ad09..635399801 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -30,6 +30,7 @@ import json import subprocess import pwd import socket +from glob import glob from xmlrpclib import Fault from importlib import import_module from collections import OrderedDict @@ -502,14 +503,9 @@ def tools_update(ignore_apps=False, ignore_packages=False): returncode = call_async_output(command, callbacks, shell=True) if returncode != 0: - - # TODO : here, we should run something like a - # `cat /etc/apt/sources.list /etc/apt/sources.list.d/*` - # and append it to the error message to improve debugging - - raise YunohostError('update_cache_failed') + raise YunohostError('update_apt_cache_failed', sourceslist='\n'.join(_dump_sources_list())) elif warnings: - logger.error(m18n.n('update_cache_warning')) + logger.error(m18n.n('update_apt_cache_warning', sourceslist='\n'.join(_dump_sources_list()))) packages = list(_list_upgradable_apt_packages()) logger.debug(m18n.n('done')) @@ -567,6 +563,17 @@ def _list_upgradable_apt_packages(): } +def _dump_sources_list(): + + filenames = glob("/etc/apt/sources.list") + glob("/etc/apt/sources.list.d/*") + for filename in filenames: + with open(filename, "r") as f: + for line in f.readlines(): + if line.startswith("#") or not line.strip(): + continue + yield filename.replace("/etc/apt/", "") + ":" + line.strip() + + @is_unit_operation() def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=False): """ From ccdd7e645d7a745d5293a188820e6e75492330c5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 28 Mar 2019 16:47:54 +0100 Subject: [PATCH 403/721] Be able to run the script from a distant folder --- data/actionsmap/yunohost_completion.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/actionsmap/yunohost_completion.py b/data/actionsmap/yunohost_completion.py index 9b5472837..915c6ac89 100644 --- a/data/actionsmap/yunohost_completion.py +++ b/data/actionsmap/yunohost_completion.py @@ -8,10 +8,12 @@ adds `--help` at the end if one presses [tab] again. author: Christophe Vuillot """ +import os import yaml -ACTIONSMAP_FILE = 'yunohost.yml' -BASH_COMPLETION_FILE = '../bash-completion.d/yunohost_completion' +THIS_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +ACTIONSMAP_FILE = THIS_SCRIPT_DIR + '/yunohost.yml' +BASH_COMPLETION_FILE = THIS_SCRIPT_DIR + '/../bash-completion.d/yunohost_completion' with open(ACTIONSMAP_FILE, 'r') as stream: From 8b8df94befe8906e0a05b6fc3afb23ac796b90c2 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Thu, 28 Mar 2019 19:37:23 +0100 Subject: [PATCH 404/721] Keep useful comments... We still use eval, comment about its usage is still relevant... --- data/helpers.d/getopts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data/helpers.d/getopts b/data/helpers.d/getopts index 894c9395f..b6cd95f3c 100644 --- a/data/helpers.d/getopts +++ b/data/helpers.d/getopts @@ -153,6 +153,13 @@ ynh_handle_getopts_args () { eval ${option_var}+="\;" fi + # For the record. + # We're using eval here to get the content of the variable stored itself as simple text in $option_var... + # Other ways to get that content would be to use either ${!option_var} or declare -g ${option_var} + # But... ${!option_var} can't be used as left part of an assignation. + # declare -g ${option_var} will create a local variable (despite -g !) and will not be available for the helper itself. + # So... Stop fucking arguing each time that eval is evil... Go find an other working solution if you can find one! + eval ${option_var}+='"${all_args[$i]}"' shift_value=$(( shift_value + 1 )) fi @@ -191,6 +198,8 @@ ynh_handle_getopts_args () { # The variable name will be stored in 'option_var' local option_var="${args_array[$option_flag]%=}" + # Store each value given as argument in the corresponding variable + # The values will be stored in the same order than $args_array eval ${option_var}+='"${arguments[$i]}"' done unset legacy_args From 15ac51098d9d2ae52eaa50929f843551310a4cd9 Mon Sep 17 00:00:00 2001 From: Romuald du Song Date: Thu, 28 Mar 2019 22:03:25 +0100 Subject: [PATCH 405/721] propose a setting to remove support for TLSv1 and TLSv1.1 --- data/hooks/conf_regen/19-postfix | 11 ++- data/templates/postfix/main.cf | 116 ++++++++++++++++--------------- locales/en.json | 1 + src/yunohost/settings.py | 7 ++ 4 files changed, 76 insertions(+), 59 deletions(-) diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix index a3ad70327..b37425984 100755 --- a/data/hooks/conf_regen/19-postfix +++ b/data/hooks/conf_regen/19-postfix @@ -2,6 +2,8 @@ set -e +. /usr/share/yunohost/helpers + do_pre_regen() { pending_dir=$1 @@ -20,9 +22,12 @@ do_pre_regen() { main_domain=$(cat /etc/yunohost/current_host) domain_list=$(sudo yunohost domain list --output-as plain --quiet | tr '\n' ' ') - cat main.cf \ - | sed "s/{{ main_domain }}/${main_domain}/g" \ - > "${postfix_dir}/main.cf" + # Support different strategy for security configurations + export compatibility="$(yunohost settings get 'security.postfix.compatibility')" + + export main_domain + export domain_list + ynh_render_template "main.cf" "${postfix_dir}/main.cf" cat postsrsd \ | sed "s/{{ main_domain }}/${main_domain}/g" \ diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index c38896a3f..e5a3875d4 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -33,7 +33,11 @@ smtpd_tls_key_file = /etc/yunohost/certs/{{ main_domain }}/key.pem smtpd_tls_exclude_ciphers = aNULL, MD5, DES, ADH, RC4, 3DES smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache smtpd_tls_loglevel=1 +{% if compatibility == "intermediate" %} smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3 +{% else %} +smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3,!TLSv1,!TLSv1.1 +{% endif %} smtpd_tls_mandatory_ciphers=high smtpd_tls_eecdh_grade = ultra @@ -58,7 +62,7 @@ alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases mydomain = {{ main_domain }} mydestination = localhost -relayhost = +relayhost = mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 mailbox_command = procmail -a "$EXTENSION" mailbox_size_limit = 0 @@ -68,71 +72,71 @@ inet_interfaces = all #### Fit to the maximum message size to 30mb, more than allowed by GMail or Yahoo #### message_size_limit = 31457280 -# Virtual Domains Control -virtual_mailbox_domains = ldap:/etc/postfix/ldap-domains.cf -virtual_mailbox_maps = ldap:/etc/postfix/ldap-accounts.cf -virtual_mailbox_base = -virtual_alias_maps = ldap:/etc/postfix/ldap-aliases.cf -virtual_alias_domains = -virtual_minimum_uid = 100 -virtual_uid_maps = static:vmail +# Virtual Domains Control +virtual_mailbox_domains = ldap:/etc/postfix/ldap-domains.cf +virtual_mailbox_maps = ldap:/etc/postfix/ldap-accounts.cf +virtual_mailbox_base = +virtual_alias_maps = ldap:/etc/postfix/ldap-aliases.cf +virtual_alias_domains = +virtual_minimum_uid = 100 +virtual_uid_maps = static:vmail virtual_gid_maps = static:mail smtpd_sender_login_maps= ldap:/etc/postfix/ldap-accounts.cf -# Dovecot LDA -virtual_transport = dovecot +# Dovecot LDA +virtual_transport = dovecot dovecot_destination_recipient_limit = 1 -# Enable SASL authentication for the smtpd daemon -smtpd_sasl_auth_enable = yes -smtpd_sasl_type = dovecot -smtpd_sasl_path = private/auth -# Fix some outlook's bugs -broken_sasl_auth_clients = yes -# Reject anonymous connections -smtpd_sasl_security_options = noanonymous +# Enable SASL authentication for the smtpd daemon +smtpd_sasl_auth_enable = yes +smtpd_sasl_type = dovecot +smtpd_sasl_path = private/auth +# Fix some outlook's bugs +broken_sasl_auth_clients = yes +# Reject anonymous connections +smtpd_sasl_security_options = noanonymous smtpd_sasl_local_domain = -# Wait until the RCPT TO command before evaluating restrictions -smtpd_delay_reject = yes - -# Basics Restrictions -smtpd_helo_required = yes -strict_rfc821_envelopes = yes - -# Requirements for the connecting server -smtpd_client_restrictions = - permit_mynetworks, - permit_sasl_authenticated, - reject_rbl_client bl.spamcop.net, - reject_rbl_client cbl.abuseat.org, - reject_rbl_client zen.spamhaus.org, - permit - -# Requirements for the HELO statement -smtpd_helo_restrictions = - permit_mynetworks, - permit_sasl_authenticated, - reject_non_fqdn_hostname, - reject_invalid_hostname, - permit - -# Requirements for the sender address +# Wait until the RCPT TO command before evaluating restrictions +smtpd_delay_reject = yes + +# Basics Restrictions +smtpd_helo_required = yes +strict_rfc821_envelopes = yes + +# Requirements for the connecting server +smtpd_client_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + reject_rbl_client bl.spamcop.net, + reject_rbl_client cbl.abuseat.org, + reject_rbl_client zen.spamhaus.org, + permit + +# Requirements for the HELO statement +smtpd_helo_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + reject_non_fqdn_hostname, + reject_invalid_hostname, + permit + +# Requirements for the sender address smtpd_sender_restrictions = - reject_sender_login_mismatch, - permit_mynetworks, - permit_sasl_authenticated, - reject_non_fqdn_sender, + reject_sender_login_mismatch, + permit_mynetworks, + permit_sasl_authenticated, + reject_non_fqdn_sender, reject_unknown_sender_domain, - permit - -# Requirement for the recipient address -smtpd_recipient_restrictions = - permit_mynetworks, - permit_sasl_authenticated, - reject_non_fqdn_recipient, - reject_unknown_recipient_domain, + permit + +# Requirement for the recipient address +smtpd_recipient_restrictions = + permit_mynetworks, + permit_sasl_authenticated, + reject_non_fqdn_recipient, + reject_unknown_recipient_domain, reject_unauth_destination, permit diff --git a/locales/en.json b/locales/en.json index 694df0707..6ab91fd2e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -215,6 +215,7 @@ "global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_security_password_user_strength": "User password strength", "global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)", + "global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)", "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/settings-unknown.json", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", diff --git a/src/yunohost/settings.py b/src/yunohost/settings.py index 671ad70e9..01f27ba83 100644 --- a/src/yunohost/settings.py +++ b/src/yunohost/settings.py @@ -44,6 +44,8 @@ DEFAULTS = OrderedDict([ "choices": ["intermediate", "modern"]}), ("security.nginx.compatibility", {"type": "enum", "default": "intermediate", "choices": ["intermediate", "modern"]}), + ("security.postfix.compatibility", {"type": "enum", "default": "intermediate", + "choices": ["intermediate", "modern"]}), ]) @@ -292,3 +294,8 @@ def reconfigure_nginx(setting_name, old_value, new_value): def reconfigure_ssh(setting_name, old_value, new_value): if old_value != new_value: service_regen_conf(names=['ssh']) + +@post_change_hook("security.postfix.compatibility") +def reconfigure_ssh(setting_name, old_value, new_value): + if old_value != new_value: + service_regen_conf(names=['postfix']) From 0a3f12ed58d37e5722311041d8e696ebd6f52150 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Sun, 31 Mar 2019 04:10:23 +0200 Subject: [PATCH 406/721] [mod] use ask key for display_text instead and support i18n --- src/yunohost/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 8561e2667..70d21afb1 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2204,7 +2204,7 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None): # do not print for webadmin if arg_type == 'display_text' and msettings.get('interface') != 'api': - print(arg["text"]) + print(_value_for_locale(arg['ask'])) continue # Attempt to retrieve argument value From 26e77b77ffe08f1c5a03eb22e9ad2f3ca0167cee Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Mon, 1 Apr 2019 21:24:15 +0200 Subject: [PATCH 407/721] Force stdout to stderr in ynh_debug Merge as a micro decision. --- data/helpers.d/debug | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/helpers.d/debug b/data/helpers.d/debug index ea20ffc1a..7ad097dbd 100644 --- a/data/helpers.d/debug +++ b/data/helpers.d/debug @@ -37,6 +37,8 @@ ynh_debug () { PS4='$(basename ${BASH_SOURCE[0]})-L${LINENO}: ' # Force xtrace to stderr BASH_XTRACEFD=2 + # Force stdout to stderr + exec 1>&2 fi if [ "$trace" == "0" ] then @@ -44,6 +46,8 @@ ynh_debug () { set +x # Put xtrace back to its original fild descriptor BASH_XTRACEFD=$old_bash_xtracefd + # Restore stdout + exec 1>&1 fi # Renable set xtrace set -x From b3d8167548803364b62ecb8cafafe0f78802497c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Apr 2019 01:53:10 +0200 Subject: [PATCH 408/721] Generate yunohost_completion.py during debian builds --- data/bash-completion.d/yunohost | 15 +---- data/bash-completion.d/yunohost_completion | 77 ---------------------- debian/rules | 4 ++ 3 files changed, 7 insertions(+), 89 deletions(-) delete mode 100644 data/bash-completion.d/yunohost_completion diff --git a/data/bash-completion.d/yunohost b/data/bash-completion.d/yunohost index 106f8fbdf..2572a391d 100644 --- a/data/bash-completion.d/yunohost +++ b/data/bash-completion.d/yunohost @@ -1,12 +1,3 @@ -# -# Bash completion for yunohost -# - -_python_argcomplete() { - local IFS=' ' - COMPREPLY=( $(IFS="$IFS" COMP_LINE="$COMP_LINE" COMP_POINT="$COMP_POINT" _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" _ARGCOMPLETE=1 "$1" 8>&1 9>&2 1>/dev/null 2>/dev/null) ) - if [[ $? != 0 ]]; then - unset COMPREPLY - fi -} -complete -o nospace -o default -F _python_argcomplete "yunohost" +# This file is automatically generated +# during Debian's package build by the script +# data/actionsmap/yunohost_completion.py diff --git a/data/bash-completion.d/yunohost_completion b/data/bash-completion.d/yunohost_completion deleted file mode 100644 index 715073475..000000000 --- a/data/bash-completion.d/yunohost_completion +++ /dev/null @@ -1,77 +0,0 @@ -# -# completion for yunohost -# automatically generated from the actionsmap -# - -_yunohost_completion() -{ - local cur prev opts narg - COMPREPLY=() - - # the number of words already typed - narg=${#COMP_WORDS[@]} - - # the current word being typed - cur="${COMP_WORDS[COMP_CWORD]}" - - # the last typed word - prev="${COMP_WORDS[COMP_CWORD-1]}" - - # If one is currently typing a domain, - # match with domains - if [[ $narg == 2 ]]; then - opts="user domain log service settings firewall backup app hook dyndns tools monitor" - fi - - # If one already typed a domain, - # match the actions of that domain - if [[ $narg == 3 ]]; then - if [[ $prev == "user" ]]; then - opts="info create list update delete" - fi - if [[ $prev == "domain" ]]; then - opts="cert-install cert-status list remove url-available add dns-conf cert-renew" - fi - if [[ $prev == "log" ]]; then - opts="list display" - fi - if [[ $prev == "service" ]]; then - opts="status enable reload_or_restart log start stop remove reload add disable regen-conf restart" - fi - if [[ $prev == "settings" ]]; then - opts="reset set list reset-all get" - fi - if [[ $prev == "firewall" ]]; then - opts="reload allow stop list upnp disallow" - fi - if [[ $prev == "backup" ]]; then - opts="info restore create list delete" - fi - if [[ $prev == "app" ]]; then - opts="map checkurl install makedefault checkport listlists change-url removelist info change-label upgrade fetchlist clearaccess ssowatconf list remove register-url removeaccess setting initdb debug addaccess" - fi - if [[ $prev == "hook" ]]; then - opts="info callback add exec list remove" - fi - if [[ $prev == "dyndns" ]]; then - opts="subscribe update installcron removecron" - fi - if [[ $prev == "tools" ]]; then - opts="upgrade ldapinit postinstall maindomain update reboot shell adminpw shutdown diagnosis port-available" - fi - if [[ $prev == "monitor" ]]; then - opts="enable network show-stats update-stats disk system disable" - fi - fi - - # If no options were found propose --help - if [ -z "$opts" ]; then - if [[ $prev != "--help" ]]; then - opts=( --help ) - fi - fi - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 -} - -complete -F _yunohost_completion yunohost \ No newline at end of file diff --git a/debian/rules b/debian/rules index ce03d0e31..d012c73f3 100755 --- a/debian/rules +++ b/debian/rules @@ -7,6 +7,10 @@ %: dh ${@} --with=python2,systemd +override_dh_auto_build: + # Generate bash completion file + python data/actionsmap/yunohost_completion.py + override_dh_installinit: dh_installinit -pyunohost --name=yunohost-api --restart-after-upgrade dh_installinit -pyunohost --name=yunohost-firewall --noscripts From 0c67559c43ba50a5d069888b1a5ea00fd3f7f21c Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 2 Apr 2019 12:33:49 +0200 Subject: [PATCH 409/721] Fix missing legacy_args Merged as a micro decision --- data/helpers.d/print | 1 + 1 file changed, 1 insertion(+) diff --git a/data/helpers.d/print b/data/helpers.d/print index 95d2af139..f1120367a 100644 --- a/data/helpers.d/print +++ b/data/helpers.d/print @@ -201,6 +201,7 @@ previous_weight=0 base_time=$(date +%s) ynh_script_progression () { # Declare an array to define the options of this helper. + local legacy_args=mwtl declare -Ar args_array=( [m]=message= [w]=weight= [t]=time [l]=last ) local message local weight From 92b5777aff64c8e7c914dea4bd050bceb6aeb32a Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 2 Apr 2019 12:36:17 +0200 Subject: [PATCH 410/721] Fix missing legacy_args Merged as a micro decision --- data/helpers.d/system | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/helpers.d/system b/data/helpers.d/system index c4c049c31..a491b19b5 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -75,6 +75,7 @@ ynh_get_debian_release () { # | arg: -e, --length= - Length of the error log : Default : 20 ynh_systemd_action() { # Declare an array to define the options of this helper. + local legacy_args=nalpte declare -Ar args_array=( [n]=service_name= [a]=action= [l]=line_match= [p]=log_path= [t]=timeout= [e]=length= ) local service_name local action @@ -175,6 +176,7 @@ ynh_clean_check_starting () { # Requires YunoHost version 3.?.? or higher. ynh_read_manifest () { # Declare an array to define the options of this helper. + local legacy_args=mk declare -Ar args_array=( [m]=manifest= [k]=manifest_key= ) local manifest local manifest_key @@ -200,6 +202,8 @@ ynh_read_manifest () { # # Requires YunoHost version 3.?.? or higher. ynh_app_upstream_version () { + # Declare an array to define the options of this helper. + local legacy_args=m declare -Ar args_array=( [m]=manifest= ) local manifest # Manage arguments with getopts @@ -221,6 +225,8 @@ ynh_app_upstream_version () { # # Requires YunoHost version 3.?.? or higher. ynh_app_package_version () { + # Declare an array to define the options of this helper. + local legacy_args=m declare -Ar args_array=( [m]=manifest= ) local manifest # Manage arguments with getopts From 9766a73e0d0fb39172984419f516bca417cb2d7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Mon, 18 Mar 2019 18:24:46 +0000 Subject: [PATCH 411/721] Translated using Weblate (French) Currently translated at 99.4% (501 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index b864c5ac7..6a41ff45b 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -471,7 +471,7 @@ "service_description_php7.0-fpm": "exécute des applications écrites en PHP avec nginx", "users_available": "Liste des utilisateurs disponibles :", "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase de chiffrement) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", - "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou d'utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", + "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères — bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou d'utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", "migration_0006_disclaimer": "Yunohost s’attend maintenant à ce que les mots de passe admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.", "migration_0006_done": "Votre mot de passe root a été remplacé par celui de votre adminitrateur.", From 92a097ea50209b0af41fb39bb03f655d57784d4b Mon Sep 17 00:00:00 2001 From: ppr Date: Tue, 19 Mar 2019 07:52:39 +0000 Subject: [PATCH 412/721] Translated using Weblate (French) Currently translated at 99.4% (501 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 58 ++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 6a41ff45b..bea929837 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,13 +1,13 @@ { - "action_invalid": "Action « {action:s} » incorrecte", + "action_invalid": "Action '{action:s}' incorrecte", "admin_password": "Mot de passe d’administration", "admin_password_change_failed": "Impossible de changer le mot de passe", "admin_password_changed": "Le mot de passe d’administration a été modifié", "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_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_argument_required": "Le paramètre '{name:s}' est requis", "app_extraction_failed": "Impossible d’extraire les fichiers d’installation", "app_id_invalid": "Identifiant d’application invalide", "app_incompatible": "L’application {app} est incompatible avec votre version de YunoHost", @@ -35,7 +35,7 @@ "appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante {appslist:s} : {error:s}", "appslist_unknown": "La liste d’applications {appslist:s} est inconnue.", "ask_current_admin_password": "Mot de passe d’administration actuel", - "ask_email": "Adresse courriel", + "ask_email": "Adresse de courriel", "ask_firstname": "Prénom", "ask_lastname": "Nom", "ask_list_to_remove": "Liste à supprimer", @@ -43,7 +43,7 @@ "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_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à", @@ -60,8 +60,8 @@ "backup_invalid_archive": "Archive de sauvegarde invalide", "backup_nothings_done": "Il n’y a rien à sauvegarder", "backup_output_directory_forbidden": "Dossier de destination interdit. Les sauvegardes ne peuvent être créées dans les sous-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_output_directory_not_empty": "Le répertoire de destination n'est pas vide", + "backup_output_directory_required": "Vous devez spécifier un dossier de destination 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 personnalisée {app:s}", @@ -255,7 +255,7 @@ "certmanager_certificate_fetching_or_enabling_failed": "Il semble que l’activation du nouveau certificat pour {domain:s} a échoué …", "certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain:s} n’est pas émis par Let’s Encrypt. Impossible de le renouveler automatiquement !", "certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain:s} est sur le point d’expirer ! Utilisez --force pour contourner cela", - "certmanager_domain_http_not_working": "Il semble que le domaine {domain:s} n’est pas accessible via HTTP. Veuillez vérifier que vos configuration DNS et Nginx sont correctes", + "certmanager_domain_http_not_working": "Il semble que le domaine {domain:s} ne soit pas accessible via HTTP. Veuillez vérifier que vos configuration DNS et Nginx sont correctes", "certmanager_error_no_A_record": "Aucun enregistrement DNS 'A' n’a été trouvé pour {domain:s}. Vous devez faire pointer votre nom de domaine vers votre machine pour être en mesure d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", "certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS 'A' du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vérificateur de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)", "certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}", @@ -264,13 +264,13 @@ "certmanager_cert_renew_success": "Renouvellement avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué", - "certmanager_no_cert_file": "Impossible de lire le fichier de certificat pour le domaine {domain:s} (fichier : {file:s})", + "certmanager_no_cert_file": "Impossible de lire le fichier de/du certificat pour le domaine {domain:s} (fichier : {file:s})", "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration Nginx {filepath:s} est en conflit et doit être préalablement retiré", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", "ldap_init_failed_to_create_admin": "L’initialisation de LDAP n’a pas réussi à créer l’utilisateur admin", "ssowat_persistent_conf_read_error": "Erreur lors de la lecture de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", - "domain_cannot_remove_main": "Impossible de supprimer le domaine principal. Commencez par définir un nouveau domaine principal", + "domain_cannot_remove_main": "Impossible de supprimer le domaine principal. Définissez d'abord un nouveau domaine principal", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", "mailbox_used_space_dovecot_down": "Le service mail Dovecot doit être démarré, si vous souhaitez voir l’espace disque occupé par la messagerie", @@ -279,7 +279,7 @@ "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez d'abord exécuter cert-install.", "certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être résolu depuis votre serveur YunoHost. Cela peut se produire si vous avez récemment modifié votre enregistrement DNS. Si c'est le cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)", "certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant l'adresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.", - "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé - veuillez réessayer plus tard.", + "certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.", "appslist_retrieve_bad_format": "Le fichier récupéré pour la liste d’applications {appslist:s} n’est pas valide", "domain_hostname_failed": "Échec de la création d’un nouveau nom d’hôte", "yunohost_ca_creation_success": "L’autorité de certification locale a été créée.", @@ -288,39 +288,39 @@ "appslist_migrating": "Migration de la liste d’applications {appslist:s} …", "appslist_could_not_migrate": "Impossible de migrer la liste {appslist:s} ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.", "appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit corrompu.", - "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Regardez avec `app changeurl` si c’est disponible.", + "app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Regardez si cela est disponible avec `app changeurl`.", "app_change_no_change_url_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL, vous pourriez avoir besoin de la mettre à jour.", - "app_change_url_failed_nginx_reload": "Le redémarrage de nginx a échoué. Voici la sortie de `nginx -t` :\n{nginx_errors:s}", - "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour (`{domain:s}{path:s}`), rien à faire.", - "app_change_url_no_script": "L’application `{app_name:s}` ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.", + "app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}", + "app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain:s}{path:s}'), rien à faire.", + "app_change_url_no_script": "L’application '{app_name:s}' ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.", "app_change_url_success": "L’URL de l’application {app:s} a été changée en {domain:s}{path:s}", - "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante\n{apps:s}", + "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante :\n{apps:s}", "app_already_up_to_date": "{app:s} est déjà à jour", "invalid_url_format": "Format d’URL non valide", - "global_settings_bad_choice_for_enum": "La valeur du paramètre {setting:s} est incorrecte. Reçu : {received_type:s} mais attendu : {expected_type:s}", - "global_settings_bad_type_for_setting": "Le type du paramètre {setting:s} est incorrect. Reçu : {received_type:s} mais attendu : {expected_type:s}", + "global_settings_bad_choice_for_enum": "La valeur du paramètre {setting:s} est incorrecte. Reçu {received_type:s} alors que {expected_type:s} était attendu", + "global_settings_bad_type_for_setting": "Le type du paramètre {setting:s} est incorrect. Reçu {received_type:s} alors que {expected_type:s} était attendu", "global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations car : {reason:s}", "global_settings_cant_serialize_setings": "Échec de sérialisation des données de configurations, cause : {reason:s}", "global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations car : {reason:s}", "global_settings_key_doesnt_exists": "La clef '{settings_key:s}' n’existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'", - "global_settings_reset_success": "Réussite ! Vos configurations précédentes ont été sauvegardées dans {path:s}", + "global_settings_reset_success": "Super ! Vos configurations précédentes ont été sauvegardées dans {path:s}", "global_settings_setting_example_bool": "Exemple d’option booléenne", "global_settings_setting_example_int": "Exemple d’option de type entier", "global_settings_setting_example_string": "Exemple d’option de type chaîne", "global_settings_setting_example_enum": "Exemple d’option de type énumération", - "global_settings_unknown_type": "Situation inattendue, la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n'est pas pris en charge par le système.", + "global_settings_unknown_type": "Situation inattendue : la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n'est pas pris en charge par le système.", "global_settings_unknown_setting_from_settings_file": "Clef inconnue dans les paramètres : '{setting_key:s}', rejet de cette clef et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", "service_conf_file_kept_back": "Le fichier de configuration « {conf} » devrait être supprimé par le service {service} mais a été conservé.", "backup_abstract_method": "Cette méthode de sauvegarde n’a pas encore été implémentée", "backup_applying_method_tar": "Création de l’archive tar de la sauvegarde …", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …", - "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans de référentiel borg-backup …", + "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le référentiel/répertoire borg-backup …", "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …", "backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde", "backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué", "backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source:s}' (nommés dans l'archive : '{dest:s}') à sauvegarder dans l'archive compressée '{archive:s}'", - "backup_ask_for_copying_if_needed": "Certains fichiers n’ont pas pu être préparés pour être sauvegardés en utilisant la méthode qui évite temporairement de gaspiller de l’espace sur le système. Pour mener la sauvegarde, {size:s} Mo doivent être temporairement utilisés. Acceptez-vous ?", + "backup_ask_for_copying_if_needed": "Certains fichiers n’ont pas pu être préparés pour être sauvegardés en utilisant la méthode qui évite temporairement de gaspiller de l’espace sur le système. Pour réaliser la sauvegarde, {size:s} Mo doivent être temporairement utilisés. Acceptez-vous ?", "backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée", "backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée", "backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive", @@ -346,8 +346,8 @@ "restore_not_enough_disk_space": "Espace disponible insuffisant (libre : {free_space:d} octets, nécessaire : {needed_space:d} octets, marge de sécurité : {margin:d} octets)", "restore_system_part_failed": "Impossible de restaurer la partie « {part:s} » du système", "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", - "domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre registrar DNS avec cette recommandation.", - "domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost, soit YunoHost n’est pas correctement connecté à internet ou alors le serveur de dynette est arrêté. Erreur : {error}", + "domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre fournisseur/registrar DNS avec cette recommandation.", + "domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost. Soit YunoHost n’est pas correctement connecté à internet, soit le serveur de dynette est en panne ou ne répond pas. Erreur : {error}", "migrations_backward": "Migration en arrière.", "migrations_bad_value_for_target": "Nombre invalide pour le paramètre « target », les numéros de migration sont ou {}", "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migrations avec le chemin %s", @@ -368,7 +368,7 @@ "ask_path": "Chemin", "dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.", "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", - "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine {domain} car déjà utilisé par l'application '{other_app}'", + "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine {domain} car il est déjà utilisé par l'application '{other_app}'", "app_upgrade_app_name": "Mise à jour de l’application {app} …", "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives '{path:s}'. Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clef USB.", "migrate_tsig_end": "La migration à hmac-sha512 est terminée", @@ -470,7 +470,7 @@ "recommend_to_add_first_user": "La post-installation est terminée, mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant « yunohost user create » ou l’interface d’administration.", "service_description_php7.0-fpm": "exécute des applications écrites en PHP avec nginx", "users_available": "Liste des utilisateurs disponibles :", - "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase de chiffrement) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", + "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères — bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou d'utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", "migration_0006_disclaimer": "Yunohost s’attend maintenant à ce que les mots de passe admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.", @@ -492,9 +492,9 @@ "ask_new_path": "Nouveau chemin", "backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés …", "backup_mount_archive_for_restore": "Préparation de l'archive pour restauration …", - "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{réponses:s}] ", + "confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{answers:s}] ", "confirm_app_install_danger": "AVERTISSEMENT ! Cette application est encore expérimentale (explicitement, elle ne fonctionne pas) et risque de casser votre système ! Vous ne devriez probablement PAS l'installer sans savoir ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{answers:s}] ", - "confirm_app_install_thirdparty": "AVERTISSEMENT ! L'installation d'applications tierces peut compromettre l'intégrité et la sécurité de votre système. Vous ne devriez probablement PAS l'installer si vous ne savez pas ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{réponses:s}] ", + "confirm_app_install_thirdparty": "AVERTISSEMENT ! L'installation d'applications tierces peut compromettre l'intégrité et la sécurité de votre système. Vous ne devriez probablement PAS l'installer si vous ne savez pas ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{answers:s}] ", "dpkg_is_broken": "Vous ne pouvez pas faire ça maintenant car dpkg/apt (le gestionnaire de paquets du système) semble avoir laissé des choses non configurées. Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo dpkg --configure -a'.", "dyndns_could_not_check_available": "Impossible de vérifier si {domain:s} est disponible chez {provider:s}.", "file_does_not_exist": "Le fichier dont le chemin est {path:s} n'existe pas.", From 63912c25f4ef986a8cb634cc050f7774fac93e31 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sun, 17 Mar 2019 16:20:39 +0000 Subject: [PATCH 413/721] Translated using Weblate (Catalan) Currently translated at 22.6% (114 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index bfad4d2bd..44d473d91 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -29,20 +29,20 @@ "app_not_properly_removed": "{app:s} no s'ha pogut suprimir correctament", "app_package_need_update": "El paquet de l'aplicació {app} ha de ser actualitzat per poder seguir els canvis de YunoHost", "app_removed": "{app:s} ha estat suprimida", - "app_requirements_checking": "Verificació dels paquets requerits per {app}", + "app_requirements_checking": "Verificació dels paquets requerits per {app}…", "app_requirements_failed": "No es poden satisfer els requeriments per {app}: {error}", "app_requirements_unmeet": "No es compleixen els requeriments per {app}, el paquet {pkgname} ({version}) ha de ser {spec}", "app_sources_fetch_failed": "No s'han pogut carregar els fitxers font", "app_unknown": "Aplicació desconeguda", "app_unsupported_remote_type": "El tipus remot utilitzat per l'aplicació no està suportat", - "app_upgrade_app_name": "Actualitzant l'aplicació {app}...", + "app_upgrade_app_name": "Actualitzant l'aplicació {app}…", "app_upgrade_failed": "No s'ha pogut actualitzar {app:s}", "app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions", "app_upgraded": "{app:s} ha estat actualitzada", "appslist_corrupted_json": "No s'han pogut carregar les llistes d'aplicacions. Sembla que {filename:s} està danyat.", "appslist_could_not_migrate": "No s'ha pogut migrar la llista d'aplicacions {appslist:s}! No s'ha pogut analitzar la URL... L'antic cronjob s'ha guardat a {bkp_file:s}.", "appslist_fetched": "S'ha descarregat la llista d'aplicacions {appslist:s} correctament", - "appslist_migrating": "Migrant la llista d'aplicacions {appslist:s} ...", + "appslist_migrating": "Migrant la llista d'aplicacions {appslist:s}…", "appslist_name_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb el nom {name:s}.", "appslist_removed": "S'ha eliminat la llista d'aplicacions {appslist:s}", "appslist_retrieve_bad_format": "L'arxiu obtingut per la llista d'aplicacions {appslist:s} no és vàlid", @@ -61,10 +61,10 @@ "backup_abstract_method": "Encara no s'ha implementat aquest mètode de copia de seguretat", "backup_action_required": "S'ha d'especificar què s'ha de guardar", "backup_app_failed": "No s'ha pogut fer la còpia de seguretat de l'aplicació \"{app:s}\"", - "backup_applying_method_borg": "Enviant tots els fitxers de la còpia de seguretat al repositori borg-backup...", - "backup_applying_method_copy": "Còpia de tots els fitxers a la còpia de seguretat...", - "backup_applying_method_custom": "Crida del mètode de còpia de seguretat personalitzat \"{method:s}\"...", - "backup_applying_method_tar": "Creació de l'arxiu tar de la còpia de seguretat...", + "backup_applying_method_borg": "Enviant tots els fitxers de la còpia de seguretat al repositori borg-backup…", + "backup_applying_method_copy": "Còpia de tots els fitxers a la còpia de seguretat…", + "backup_applying_method_custom": "Crida del mètode de còpia de seguretat personalitzat \"{method:s}\"…", + "backup_applying_method_tar": "Creació de l'arxiu tar de la còpia de seguretat…", "backup_archive_app_not_found": "L'aplicació \"{app:s}\" no es troba dins l'arxiu de la còpia de seguretat", "backup_archive_broken_link": "No s'ha pogut accedir a l'arxiu de la còpia de seguretat (enllaç invàlid cap a {path:s})", "backup_archive_mount_failed": "No s'ha pogut carregar l'arxiu de la còpia de seguretat", @@ -80,5 +80,37 @@ "backup_copying_to_organize_the_archive": "Copiant {size:s}MB per organitzar l'arxiu", "backup_couldnt_bind": "No es pot lligar {src:s} amb {dest:s}.", "backup_created": "S'ha creat la còpia de seguretat", - "backup_creating_archive": "Creant l'arxiu de la còpia de seguretat" + "backup_creating_archive": "Creant l'arxiu de la còpia de seguretat…", + "aborting": "Avortant.", + "app_not_upgraded": "Les següents aplicacions no s'han actualitzat: {apps}", + "app_start_install": "instal·lant l'aplicació {app}…", + "app_start_remove": "Eliminant l'aplicació {app}…", + "app_start_backup": "Recuperant els fitxers pels que s'ha de fer una còpia de seguretat per {app}…", + "app_start_restore": "Recuperant l'aplicació {app}…", + "app_upgrade_several_apps": "S'actualitzaran les següents aplicacions: {apps}", + "ask_new_domain": "Nou domini", + "ask_new_path": "Nou camí", + "backup_actually_backuping": "S'està creant un arxiu de còpia de seguretat a partir dels fitxers recuperats…", + "backup_creation_failed": "Ha fallat la creació de la còpia de seguretat", + "backup_csv_addition_failed": "No s'han pogut afegir fitxers per a fer-ne la còpia de seguretat al fitxer CSV", + "backup_csv_creation_failed": "No s'ha pogut crear el fitxer CSV necessari per a futures operacions de recuperació", + "backup_custom_backup_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"backup\"", + "backup_custom_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"mount\"", + "backup_custom_need_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"need_mount\"", + "backup_delete_error": "No s'ha pogut suprimir \"{path:s}\"", + "backup_deleted": "S'ha suprimit la còpia de seguretat", + "backup_extracting_archive": "Extraient l'arxiu de la còpia de seguretat…", + "backup_hook_unknown": "Script de còpia de seguretat \"{hook:s}\" desconegut", + "backup_invalid_archive": "Arxiu de còpia de seguretat no vàlid", + "backup_method_borg_finished": "La còpia de seguretat a borg ha acabat", + "backup_method_copy_finished": "La còpia de la còpia de seguretat ha acabat", + "backup_method_custom_finished": "El mètode de còpia de seguretat personalitzat \"{method:s}\" ha acabat", + "backup_method_tar_finished": "S'ha creat l'arxiu de còpia de seguretat tar", + "backup_mount_archive_for_restore": "Preparant l'arxiu per la restauració…", + "good_practices_about_user_password": "Esteu a punt de definir una nova contrasenya d'usuari. La contrasenya ha de tenir un mínim de 8 caràcters ; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).", + "password_listed": "Aquesta contrasenya és una de les més utilitzades en el món. Si us plau utilitzeu-ne una més única.", + "password_too_simple_1": "La contrasenya ha de tenir un mínim de 8 caràcters", + "password_too_simple_2": "La contrasenya ha de tenir un mínim de 8 caràcters i ha de contenir dígits, majúscules i minúscules", + "password_too_simple_3": "La contrasenya ha de tenir un mínim de 8 caràcters i tenir dígits, majúscules, minúscules i caràcters especials", + "password_too_simple_4": "La contrasenya ha de tenir un mínim de 12 caràcters i tenir dígits, majúscules, minúscules i caràcters especials" } From 6523caffcbd473350c133d2e115adadf70920055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Mon, 18 Mar 2019 20:09:52 +0000 Subject: [PATCH 414/721] Translated using Weblate (Esperanto) Currently translated at 6.7% (34 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index 6a7a82784..b9d973557 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -16,7 +16,7 @@ "yunohost_already_installed": "YunoHost estas jam instalita", "yunohost_ca_creation_failed": "Ne eblas krei atestan aŭtoritaton", "yunohost_ca_creation_success": "Loka atesta aŭtoritato estas kreita.", - "yunohost_installing": "Instalata YunoHost...", + "yunohost_installing": "Instalante YunoHost…", "service_description_glances": "monitoras sisteminformojn de via servilo", "service_description_metronome": "mastrumas XMPP tujmesaĝilon kontojn", "service_description_mysql": "stokas aplikaĵojn datojn (SQL datumbazo)", From a4f00f2eff9de781d64e496d748d26dfbf67af3f Mon Sep 17 00:00:00 2001 From: ppr Date: Tue, 19 Mar 2019 09:06:19 +0000 Subject: [PATCH 415/721] Translated using Weblate (French) Currently translated at 97.4% (491 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 234 ++++++++++++++++++++++++------------------------ 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index bea929837..58c25d3ad 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -111,7 +111,7 @@ "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 correctement", - "hook_list_by_invalid": "La propriété de tri des actions est invalide", + "hook_list_by_invalid": "Propriété invalide pour lister les actions par", "hook_name_unknown": "Nom de l'action '{name:s}' inconnu", "installation_complete": "Installation terminée", "installation_failed": "Échec de l’installation", @@ -119,8 +119,8 @@ "iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge", "ldap_initialized": "L’annuaire LDAP a été initialisé", "license_undefined": "indéfinie", - "mail_alias_remove_failed": "Impossible de supprimer l’alias courriel '{mail:s}'", - "mail_domain_unknown": "Le domaine '{domain:s}' du courriel est inconnu", + "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", + "mail_domain_unknown": "Le domaine '{domain:s}' est inconnu pour cette adresse de courriel", "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "maindomain_change_failed": "Impossible de modifier le domaine principal", "maindomain_changed": "Le domaine principal a été modifié", @@ -136,119 +136,119 @@ "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": "La base de données MySQL a été initialisée", - "network_check_mx_ko": "L’enregistrement DNS MX n’est pas précisé", + "network_check_mx_ko": "L’enregistrement DNS MX n’est pas défini", "network_check_smtp_ko": "Le trafic courriel 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 n’a été 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_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", + "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 s'est produite lors du traitement du paquet '{pkgname}'", + "package_unknown": "Le paquet '{pkgname}' est 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 avec un maximum de 30 caractères, et composé uniquement de caractères alphanumériques et de tirets tels que - et _", - "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)", - "pattern_email": "Doit être une adresse courriel valide (ex. : pseudo@domain.org)", + "pattern_backup_archive_name": "Doit être un nom de fichier valide avec un maximum de 30 caractères, et composé uniquement de caractères alphanumériques et de tirets tels que -_.", + "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.fr)", + "pattern_email": "Doit être une adresse de courriel valide (ex. : pseudo@domaine.fr)", "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_listname": "Doit être composé uniquement de caractères alphanumériques et de tirets bas (aussi appelé tiret du 8 ou underscore)", + "pattern_mailbox_quota": "Doit avoir une taille suffixée avec 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_port": "Doit être un numéro de port valide compris entre 0 et 65535", + "pattern_port_or_range": "Doit être un numéro de port valide compris entre 0 et 65535, ou une gamme de ports (exemple : 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", + "pattern_username": "Doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas (aussi appelé tiret du 8 ou underscore)", "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_already_installed_app": "Une application est déjà installée avec l’identifiant '{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 « {part:s} » n’est pas disponible sur votre système, et n’est pas non plus dans l’archive", + "restore_hook_unavailable": "Le script de restauration '{part:s}' n’est pas disponible sur votre système, et ne l'est pas non plus dans l’archive", "restore_nothings_done": "Rien n’a été restauré", "restore_running_app_script": "Exécution du script de restauration de 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": "Le service « {service:s} » a été ajouté", - "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_conf_file_backed_up": "Le fichier de configuration « {conf} » a été sauvegardé dans « {backup} »", - "service_conf_file_copy_failed": "Impossible de copier le nouveau fichier de configuration « {new} » vers « {conf} »", - "service_conf_file_manually_modified": "Le fichier de configuration « {conf} » a été modifié manuellement et ne sera pas mis à jour", - "service_conf_file_manually_removed": "Le fichier de configuration « {conf} » a été supprimé manuellement et ne sera pas créé", + "service_add_failed": "Impossible d’ajouter le service '{service:s}'", + "service_added": "Le service '{service:s}' a été ajouté", + "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_conf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé dans '{backup}'", + "service_conf_file_copy_failed": "Impossible de copier le nouveau fichier de configuration '{new}' vers '{conf}'", + "service_conf_file_manually_modified": "Le fichier de configuration '{conf}' a été modifié manuellement et ne sera pas mis à jour", + "service_conf_file_manually_removed": "Le fichier de configuration '{conf}' a été supprimé manuellement et ne sera pas créé", "service_conf_file_not_managed": "Le fichier de configuration « {conf} » n'est pas géré pour l'instant et ne sera pas mis à jour", - "service_conf_file_remove_failed": "Impossible de supprimer le fichier de configuration « {conf} »", - "service_conf_file_removed": "Le fichier de configuration « {conf} » a été supprimé", - "service_conf_file_updated": "Le fichier de configuration « {conf} » a été mis à jour", - "service_conf_up_to_date": "La configuration du service « {service} » est déjà à jour", - "service_conf_updated": "La configuration a été mise à jour pour le service « {service} »", - "service_conf_would_be_updated": "La configuration du service « {service} » aurait été mise à jour", + "service_conf_file_remove_failed": "Impossible de supprimer le fichier de configuration '{conf}'", + "service_conf_file_removed": "Le fichier de configuration '{conf}' a été supprimé", + "service_conf_file_updated": "Le fichier de configuration '{conf}' a été mis à jour", + "service_conf_up_to_date": "La configuration du service '{service}' est déjà à jour", + "service_conf_updated": "La configuration a été mise à jour pour le service '{service}'", + "service_conf_would_be_updated": "La configuration du service '{service}' aurait été mise à jour", "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} »\n\nJournaux récents : {logs:s}", - "service_disabled": "Le service « {service:s} » a été désactivé", - "service_enable_failed": "Impossible d’activer le service « {service:s} »\n\nJournaux récents : {logs:s}", - "service_enabled": "Le service « {service:s} » a été activé", - "service_no_log": "Aucun journal à afficher pour le service « {service:s} »", - "service_regenconf_dry_pending_applying": "Vérification des configurations en attentes qui pourraient être appliquées pour le service « {service} »…", + "service_disable_failed": "Impossible de désactiver le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", + "service_disabled": "Le service '{service:s}' a été désactivé", + "service_enable_failed": "Impossible d’activer le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", + "service_enabled": "Le service '{service:s}' a été activé", + "service_no_log": "Aucun journal historisé à afficher pour le service '{service:s}'", + "service_regenconf_dry_pending_applying": "Vérification des configurations en attentes qui pourraient être appliquées au le service '{service}' …", "service_regenconf_failed": "Impossible de régénérer la configuration pour les services : {services}", - "service_regenconf_pending_applying": "Application des configurations en attentes pour le service « {service} »…", - "service_remove_failed": "Impossible d’enlever le service « {service:s} »", - "service_removed": "Le service « {service:s} » a été enlevé", - "service_start_failed": "Impossible de démarrer le service « {service:s} »\n\nJournaux récents : {logs:s}", - "service_started": "Le service « {service:s} » a été démarré", - "service_status_failed": "Impossible de déterminer le statut du service « {service:s} »", - "service_stop_failed": "Impossible d’arrêter le service « {service:s} »\n\nJournaux récents : {logs:s}", - "service_stopped": "Le service « {service:s} » a été arrêté", - "service_unknown": "Service « {service:s} » inconnu", + "service_regenconf_pending_applying": "Application des configurations en attentes pour le service '{service}' …", + "service_remove_failed": "Impossible de supprimer le service '{service:s}'", + "service_removed": "Le service '{service:s}' a été supprimé", + "service_start_failed": "Impossible de démarrer le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", + "service_started": "Le service '{service:s}' a été démarré", + "service_status_failed": "Impossible de déterminer le statut du service '{service:s}'", + "service_stop_failed": "Impossible d’arrêter le service '{service:s}'\n\nJournaux historisés récents : {logs:s}", + "service_stopped": "Le service '{service:s}' a été arrêté", + "service_unknown": "Le service '{service:s}' est 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": "La configuration de SSOwat a été générée", "ssowat_conf_updated": "La configuration de SSOwat a été mise à jour", "system_upgraded": "Le système a été mis à jour", - "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", + "system_username_exists": "Ce 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 : {error}", + "unit_unknown": "L'unité '{unit:s}' est 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": "Récupération des mises à jour disponibles pour les paquets du système .…", + "unrestore_app": "L’application '{app:s}' ne sera pas restaurée", + "update_cache_failed": "Impossible de mettre à jour le cache de l'outil de gestion avancée des paquets (APT)", + "updating_apt_cache": "Récupération des mises à jour disponibles pour les paquets du système …", "upgrade_complete": "Mise à jour terminée", "upgrading_packages": "Mise à jour des paquets en cours …", "upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé", "upnp_disabled": "UPnP a été désactivé", "upnp_enabled": "UPnP a été activé", - "upnp_port_open_failed": "Impossible d’ouvrir les ports avec UPnP", + "upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP", "user_created": "L’utilisateur a été créé", "user_creation_failed": "Impossible de créer l’utilisateur", "user_deleted": "L’utilisateur a été supprimé", "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_unknown": "L'utilisateur {user:s} est inconnu", "user_update_failed": "Impossible de modifier l’utilisateur", "user_updated": "L’utilisateur a été modifié", "yunohost_already_installed": "YunoHost est déjà installé", "yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification", "yunohost_configured": "YunoHost a été configuré", - "yunohost_installing": "Installation de YunoHost en cours …", - "yunohost_not_installed": "YunoHost n’est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall »", + "yunohost_installing": "L'installation de YunoHost est en cours …", + "yunohost_not_installed": "YunoHost n’est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'", "certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)", "certmanager_domain_unknown": "Domaine {domain:s} inconnu", "certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)", @@ -267,13 +267,13 @@ "certmanager_no_cert_file": "Impossible de lire le fichier de/du certificat pour le domaine {domain:s} (fichier : {file:s})", "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration Nginx {filepath:s} est en conflit et doit être préalablement retiré", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", - "ldap_init_failed_to_create_admin": "L’initialisation de LDAP n’a pas réussi à créer l’utilisateur admin", + "ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin", "ssowat_persistent_conf_read_error": "Erreur lors de la lecture de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON", "domain_cannot_remove_main": "Impossible de supprimer le domaine principal. Définissez d'abord un nouveau domaine principal", "certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})", "certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})", - "mailbox_used_space_dovecot_down": "Le service mail Dovecot doit être démarré, si vous souhaitez voir l’espace disque occupé par la messagerie", + "mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré, si vous souhaitez voir l’espace disque occupé par la messagerie", "domains_available": "Domaines disponibles :", "backup_archive_broken_link": "Impossible d’accéder à l’archive de sauvegarde (lien invalide vers {path:s})", "certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez d'abord exécuter cert-install.", @@ -311,7 +311,7 @@ "global_settings_unknown_type": "Situation inattendue : la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n'est pas pris en charge par le système.", "global_settings_unknown_setting_from_settings_file": "Clef inconnue dans les paramètres : '{setting_key:s}', rejet de cette clef et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", - "service_conf_file_kept_back": "Le fichier de configuration « {conf} » devrait être supprimé par le service {service} mais a été conservé.", + "service_conf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par le service {service} mais a été conservé.", "backup_abstract_method": "Cette méthode de sauvegarde n’a pas encore été implémentée", "backup_applying_method_tar": "Création de l’archive tar de la sauvegarde …", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …", @@ -340,28 +340,28 @@ "backup_with_no_restore_script_for_app": "L’application {app:s} n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.", "global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}", "restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire", - "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive…", - "restore_mounting_archive": "Montage de l’archive dans « {path:s} »", - "restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment d’espace disponible (libre : {free_space:d} octets, nécessaire : {needed_space:d} octets, marge de sécurité : {margin:d} octets)", - "restore_not_enough_disk_space": "Espace disponible insuffisant (libre : {free_space:d} octets, nécessaire : {needed_space:d} octets, marge de sécurité : {margin:d} octets)", - "restore_system_part_failed": "Impossible de restaurer la partie « {part:s} » du système", + "restore_extracting": "Extraction des fichiers nécessaires depuis l’archive …", + "restore_mounting_archive": "Montage de l’archive dans '{path:s}'", + "restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment d’espace disponible (L'espace libre est de {free_space:d} octets. Le besoin d'espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin:d} octets)", + "restore_not_enough_disk_space": "Espace disponible insuffisant (L'espace libre est de {free_space:d} octets. Le besoin d'espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin:d} octets)", + "restore_system_part_failed": "Impossible de restaurer la partie '{part:s}' du système", "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", "domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre fournisseur/registrar DNS avec cette recommandation.", "domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost. Soit YunoHost n’est pas correctement connecté à internet, soit le serveur de dynette est en panne ou ne répond pas. Erreur : {error}", "migrations_backward": "Migration en arrière.", - "migrations_bad_value_for_target": "Nombre invalide pour le paramètre « target », les numéros de migration sont ou {}", + "migrations_bad_value_for_target": "Nombre invalide pour le paramètre target, les numéros de migration sont 0 ou {}", "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migrations avec le chemin %s", "migrations_current_target": "La cible de migration est {}", "migrations_error_failed_to_load_migration": "ERREUR : échec du chargement de migration {number} {name}", "migrations_forward": "Migration en avant", - "migrations_loading_migration": "Chargement de la migration {number} {name}…", - "migrations_migration_has_failed": "La migration {number} {name} a échoué avec l’exception {exception}, annulation", + "migrations_loading_migration": "Chargement de la migration {number} {name} …", + "migrations_migration_has_failed": "La migration {number} {name} a échoué avec l’exception {exception} : annulation", "migrations_no_migrations_to_run": "Aucune migration à lancer", - "migrations_show_currently_running_migration": "Application de la migration {number} {name}…", + "migrations_show_currently_running_migration": "Application de la migration {number} {name} …", "migrations_show_last_migration": "La dernière migration appliquée est {}", - "migrations_skip_migration": "Omission de la migration {number} {name}…", - "server_shutdown": "Le serveur sera éteint", - "server_shutdown_confirm": "Le serveur immédiatement être éteint, le voulez-vous vraiment ? [{answers:s}]", + "migrations_skip_migration": "Ignorer et passer la migration {number} {name} …", + "server_shutdown": "Le serveur va éteindre", + "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]", "server_reboot": "Le serveur va redémarrer", "server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]", "app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications", @@ -372,35 +372,35 @@ "app_upgrade_app_name": "Mise à jour de l’application {app} …", "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives '{path:s}'. Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clef USB.", "migrate_tsig_end": "La migration à hmac-sha512 est terminée", - "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué, annulation des modifications. Erreur : {error_code} - {error}", - "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine « {domain} », lancement de la migration vers hmac-sha512 qui est plus sécurisé", + "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", + "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers hmac-sha512 qui est plus sécurisé", "migrate_tsig_wait": "Attendre 3 minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …", "migrate_tsig_wait_2": "2 minutes …", "migrate_tsig_wait_3": "1 minute …", "migrate_tsig_wait_4": "30 secondes …", "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire !", "app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' est obsolète ! Utilisez 'app register-url' en remplacement !", - "migration_description_0001_change_cert_group_to_sslcert": "Change les permissions de groupe des certificats de 'metronome' à 'ssl-cert'", - "migration_description_0002_migrate_to_tsig_sha256": "Améliore la sécurité de DynDNS TSIG en utilisant SHA512 au lieu de MD5", + "migration_description_0001_change_cert_group_to_sslcert": "Changement des permissions de groupe des certificats de 'metronome' à 'ssl-cert'", + "migration_description_0002_migrate_to_tsig_sha256": "Amélioration de la sécurité de DynDNS TSIG en utilisant SHA512 au lieu de MD5", "migration_description_0003_migrate_to_stretch": "Mise à niveau du système vers Debian Stretch et YunoHost 3.0", "migration_0003_backward_impossible": "La migration Stretch n’est pas réversible.", "migration_0003_start": "Démarrage de la migration vers Stretch. Les journaux seront disponibles dans {logfile}.", - "migration_0003_patching_sources_list": "Modification de sources.lists …", + "migration_0003_patching_sources_list": "Modification du fichier sources.lists …", "migration_0003_main_upgrade": "Démarrage de la mise à niveau principale …", "migration_0003_fail2ban_upgrade": "Démarrage de la mise à niveau de fail2ban …", "migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié d’une manière ou d’une autre. La migration va d’abords le réinitialiser à son état initial. Le fichier précédent sera disponible en tant que {backup_dest}.", - "migration_0003_yunohost_upgrade": "Démarrage de la mise à niveau du paquet YunoHost. La migration se terminera, mais la mise à jour réelle aura lieu immédiatement après. Après cette opération terminée, vous pourriez avoir à vous reconnecter à l’administration via le panel web.", + "migration_0003_yunohost_upgrade": "Démarrage de la mise à niveau du paquet YunoHost. La migration se terminera, mais la mise à jour réelle aura lieu immédiatement après. Une fois cette opération terminée, vous pourriez avoir à vous reconnecter à l’administration via le panel web.", "migration_0003_not_jessie": "La distribution Debian actuelle n’est pas Jessie !", "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", - "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est ma passé pendant la mise à niveau principale : le système est toujours sur Jessie ?!? Pour investiguer le problème, veuillez regarder les journaux {log} 🙁…", - "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nDe plus, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence !", - "migration_0003_problematic_apps_warning": "Veuillez noter que les applications suivantes, éventuellement problématiques, ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées «working ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", + "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Jessie !? Pour en savoir plus et investiguer sur ce problème, veuillez regarder les journaux {log}:s .", + "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence !", + "migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme 'fonctionnelles'/'working'. En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", - "migrations_to_be_ran_manually": "La migration {number} {name} doit être lancée manuellement. Veuillez aller dans Outils > Migration dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", + "migrations_to_be_ran_manually": "La migration {number} {name} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", "migrations_need_to_accept_disclaimer": "Pour lancer la migration {number} {name}, vous devez accepter cette clause de non-responsabilité :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.", "service_description_avahi-daemon": "permet d’atteindre votre serveur via yunohost.local sur votre réseau local", - "service_description_dnsmasq": "assure la résolution des noms de domaine (DNS)", + "service_description_dnsmasq": "gère la résolution des noms de domaine (DNS)", "service_description_dovecot": "permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)", "service_description_fail2ban": "protège contre les attaques brute-force et autres types d’attaques venant d’Internet", "service_description_glances": "surveille les informations système de votre serveur", @@ -410,43 +410,43 @@ "service_description_nslcd": "gère la connexion en ligne de commande des utilisateurs YunoHost", "service_description_php5-fpm": "exécute des applications écrites en PHP avec nginx", "service_description_postfix": "utilisé pour envoyer et recevoir des courriels", - "service_description_redis-server": "une base de donnée spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication inter-programmes", + "service_description_redis-server": "une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes", "service_description_rmilter": "vérifie divers paramètres dans les courriels", "service_description_rspamd": "filtre le pourriel, et d’autres fonctionnalités liées au courriel", "service_description_slapd": "stocke les utilisateurs, domaines et leurs informations liées", "service_description_ssh": "vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)", "service_description_yunohost-api": "permet les interactions entre l’interface web de YunoHost et le système", - "service_description_yunohost-firewall": "gère les ports de connexion ouverts et fermés aux services", + "service_description_yunohost-firewall": "gère l'ouverture et la fermeture des ports de connexion aux services", "experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.", - "log_corrupted_md_file": "Le fichier yaml de metadata associé aux logs est corrompu : {md_file}", + "log_corrupted_md_file": "Le fichier yaml de metadata associé aux logs est corrompu : '{md_file}'", "log_category_404": "Le journal de la catégorie '{category}' n’existe pas", - "log_link_to_log": "Log complet de cette opération : ' {desc} '", - "log_help_to_get_log": "Pour voir le log de cette opération '{desc}', utiliser la commande 'yunohost log display {name}'", - "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de fournir le log complet de l’opération en cliquant ici", + "log_link_to_log": "Journal historisé complet de cette opération : ' {desc} '", + "log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'", + "log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de fournir le journal historisé complet de l’opération en cliquant ici", "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge php7, vos applications php pourraient ne pas être restaurées (reason: {error:s})", - "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le log de cette opération en utilisant la commande 'yunohost log display {name} --share'", - "log_does_exists": "Il n’existe pas de log de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de logs disponibles'", - "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", - "log_app_addaccess": "Ajouter l’accès à '{}'", - "log_app_removeaccess": "Enlever l’accès à '{}'", + "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le journal historisé de cette opération en utilisant la commande 'yunohost log display {name} --share'", + "log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'", + "log_operation_unit_unclosed_properly": "L’opération ne s’est pas fermée/terminée correctement", + "log_app_addaccess": "Ajouter l’accès à '{}'", + "log_app_removeaccess": "Enlever l’accès à '{}'", "log_app_clearaccess": "Retirer tous les accès à '{}'", "log_app_fetchlist": "Ajouter une liste d’application", "log_app_removelist": "Enlever une liste d’application", - "log_app_change_url": "Changer l’url de l’application '{}'", + "log_app_change_url": "Changer l’URL de l’application '{}'", "log_app_install": "Installer l’application '{}'", "log_app_remove": "Enlever l’application '{}'", "log_app_upgrade": "Mettre à jour l’application '{}'", - "log_app_makedefault": "Faire de '{}' l’application par défaut", - "log_available_on_yunopaste": "Le log est désormais disponible via {url}", + "log_app_makedefault": "Faire de '{}' l’application par défaut", + "log_available_on_yunopaste": "Le journal historisé est désormais disponible via {url}", "log_backup_restore_system": "Restaurer le système depuis une archive de sauvegarde", "log_backup_restore_app": "Restaurer '{}' depuis une sauvegarde", - "log_remove_on_failed_restore": "Retirer '{}' après la restauration depuis une sauvegarde qui a échouée", + "log_remove_on_failed_restore": "Retirer '{}' après un échec de restauration depuis une archive de sauvegarde", "log_remove_on_failed_install": "Enlever '{}' après une installation échouée", "log_domain_add": "Ajouter le domaine '{}' dans la configuration du système", "log_domain_remove": "Enlever le domaine '{}' de la configuration du système", "log_dyndns_subscribe": "Souscrire au sous-domaine YunoHost '{}'", "log_dyndns_update": "Mettre à jour l’adresse IP associée à votre sous-domaine YunoHost '{}'", - "log_letsencrypt_cert_install": "Installer le certificat Let’s Encrypt sur le domaine '{}'", + "log_letsencrypt_cert_install": "Installer le certificat Let’s Encrypt sur le domaine '{}'", "log_selfsigned_cert_install": "Installer le certificat auto-signé sur le domaine '{}'", "log_letsencrypt_cert_renew": "Renouveler le certificat Let’s Encrypt de '{}'", "log_service_enable": "Activer le service '{}'", @@ -461,26 +461,26 @@ "log_tools_upgrade": "Mise à jour des paquets Debian", "log_tools_shutdown": "Éteindre votre serveur", "log_tools_reboot": "Redémarrer votre serveur", - "mail_unavailable": "Cette adresse mail est réservée et doit être automatiquement attribuée au tout premier utilisateur", - "migration_description_0004_php5_to_php7_pools": "Reconfigurez le pool PHP pour utiliser PHP 7 au lieu de 5", - "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de postgresql 9.4 vers 9.6", - "migration_0005_postgresql_94_not_installed": "Postgresql n’a pas été installé sur votre système. Rien à faire !", - "migration_0005_postgresql_96_not_installed": "Postgresql 9.4 a été trouvé et installé, mais pas Postgresql 9.6 !? Quelque chose d’étrange a dû arriver à votre système :( …", + "mail_unavailable": "Cette adresse de courriel est réservée et doit être automatiquement attribuée au tout premier utilisateur", + "migration_description_0004_php5_to_php7_pools": "Reconfigurez le pool PHP pour utiliser PHP 7 au lieu de PHP 5", + "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", + "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", + "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système :( …", "migration_0005_not_enough_space": "Il n’y a pas assez d’espace libre de disponible sur {path} pour lancer maintenant la migration :(.", - "recommend_to_add_first_user": "La post-installation est terminée, mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant « yunohost user create » ou l’interface d’administration.", - "service_description_php7.0-fpm": "exécute des applications écrites en PHP avec nginx", + "recommend_to_add_first_user": "La post-installation est terminée. YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant 'yunohost user create' ou bien via l’interface d’administration web.", + "service_description_php7.0-fpm": "exécute des applications écrites en PHP avec Nginx", "users_available": "Liste des utilisateurs disponibles :", "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères — bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou d'utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", - "migration_0006_disclaimer": "Yunohost s’attend maintenant à ce que les mots de passe admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.", + "migration_0006_disclaimer": "YunoHost s’attendra à ce que les mots de passe admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.", "migration_0006_done": "Votre mot de passe root a été remplacé par celui de votre adminitrateur.", - "password_listed": "Ce mot de passe est l'un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d'un peu plus unique.", + "password_listed": "Ce mot de passe est l'un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d'un peu plus singulier.", "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules", "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", - "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager sur le mot de passe root !", + "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager au mot de passe root !", "aborting": "Interruption de la procédure.", "app_not_upgraded": "Les applications suivantes n'ont pas été mises à jour : {apps}", "app_start_install": "Installation de l'application {app} …", @@ -516,11 +516,11 @@ "pattern_password_app": "Désolé, les mots de passe ne doivent pas contenir les caractères suivants : {forbidden_chars}", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", "service_conf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost.", - "service_reload_failed": "Impossible de recharger le service '{service:s}'.\n\nJournaux récents de ce service : {logs:s}", + "service_reload_failed": "Impossible de recharger le service '{service:s}'.\n\nJournaux historisés récents de ce service : {logs:s}", "service_reloaded": "Le service '{service:s}' a été rechargé", - "service_restart_failed": "Impossible de redémarrer le service '{service:s}'\n\nJournaux récents de ce service : {logs:s}", + "service_restart_failed": "Impossible de redémarrer le service '{service:s}'\n\nJournaux historisés récents de ce service : {logs:s}", "service_restarted": "Le service '{service:s}' a été redémarré", - "service_reload_or_restart_failed": "Impossible de recharger ou de redémarrer le service '{service:s}'\n\nJournaux récents de ce service : {logs:s}", + "service_reload_or_restart_failed": "Impossible de recharger ou de redémarrer le service '{service:s}'\n\nJournaux historisés récents de ce service : {logs:s}", "service_reloaded_or_restarted": "Le service '{service:s}' a été rechargé ou redémarré", - "this_action_broke_dpkg": "Cette action a laissé des paquets non configurés par dpkg/apt (les gestionnaires de paquets système). Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo dpkg --configure -a'." + "this_action_broke_dpkg": "Cette action a laissé des paquets non configurés par dpkg/apt (les gestionnaires de paquets système). Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo dpkg --configure -a`." } From 8954caa39d22cf59f07f90aa6d7ede925a2b60a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Sun, 17 Mar 2019 18:44:20 +0000 Subject: [PATCH 416/721] Translated using Weblate (Occitan) Currently translated at 95.6% (482 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 48 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/locales/oc.json b/locales/oc.json index 91b455210..433da4478 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -13,7 +13,7 @@ "app_not_properly_removed": "{app:s} es pas estat corrèctament suprimit", "app_removed": "{app:s} es estat suprimit", "app_unknown": "Aplicacion desconeguda", - "app_upgrade_app_name": "Mesa a jorn de l’aplicacion {app}...", + "app_upgrade_app_name": "Mesa a jorn de l’aplicacion {app}…", "app_upgrade_failed": "Impossible de metre a jorn {app:s}", "app_upgrade_some_app_failed": "D’aplicacions se pòdon pas metre a jorn", "app_upgraded": "{app:s} es estat mes a jorn", @@ -52,7 +52,7 @@ "app_location_already_used": "L’aplicacion « {app} » es ja installada a aqueste emplaçament ({path})", "app_manifest_invalid": "Manifest d’aplicacion incorrècte : {error}", "app_package_need_update": "Lo paquet de l’aplicacion {app} deu èsser mes a jorn per seguir los cambiaments de YunoHost", - "app_requirements_checking": "Verificacion dels paquets requesida per {app}...", + "app_requirements_checking": "Verificacion dels paquets requesits per {app}…", "app_sources_fetch_failed": "Recuperacion dels fichièrs fonts impossibla", "app_unsupported_remote_type": "Lo tipe alonhat utilizat per l’aplicacion es pas suportat", "appslist_retrieve_error": "Impossible de recuperar la lista d’aplicacions alonhadas {appslist:s} : {error:s}", @@ -64,7 +64,7 @@ "backup_cleaning_failed": "Impossible de netejar lo repertòri temporari de salvagarda", "backup_copying_to_organize_the_archive": "Còpia de {size:s} Mio per organizar l’archiu", "backup_created": "Salvagarda acabada", - "backup_creating_archive": "Creacion de l’archiu de salvagarda...", + "backup_creating_archive": "Creacion de l’archiu de salvagarda…", "backup_creation_failed": "Impossible de crear la salvagarda", "app_already_installed_cant_change_url": "Aquesta aplicacion es ja installada. Aquesta foncion pòt pas simplament cambiar l’URL. Agachatz « app changeurl » s’es disponible.", "app_change_no_change_url_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, poiretz aver de la metre a jorn.", @@ -83,7 +83,7 @@ "backup_output_directory_not_empty": "Lo dorsièr de sortida es pas void", "backup_output_directory_required": "Vos cal especificar un dorsièr de sortida per la salvagarda", "backup_running_app_script": "Lançament de l’escript de salvagarda de l’aplicacion « {app:s} »...", - "backup_running_hooks": "Execucion dels scripts de salvagarda...", + "backup_running_hooks": "Execucion dels scripts de salvagarda…", "backup_system_part_failed": "Impossible de salvagardar la part « {part:s} » del sistèma", "app_requirements_failed": "Impossible de complir las condicions requesidas per {app} : {error}", "app_requirements_unmeet": "Las condicions requesidas per {app} son pas complidas, lo paquet {pkgname} ({version}) deu èsser {spec}", @@ -112,7 +112,7 @@ "upnp_port_open_failed": "Impossible de dobrir los pòrts amb UPnP", "yunohost_already_installed": "YunoHost es ja installat", "yunohost_configured": "YunoHost es estat configurat", - "yunohost_installing": "Installacion de YunoHost...", + "yunohost_installing": "Installacion de YunoHost…", "backup_applying_method_borg": "Mandadís de totes los fichièrs a la salvagarda dins lo repertòri borg-backup…", "backup_csv_creation_failed": "Creacion impossibla del fichièr CSV necessari a las operacions futuras de restauracion", "backup_extracting_archive": "Extraccion de l’archiu de salvagarda…", @@ -161,7 +161,7 @@ "dyndns_cron_removed": "La tasca cron pel domeni DynDNS es levada", "dyndns_ip_update_failed": "Impossible d’actualizar l’adreça IP sul domeni DynDNS", "dyndns_ip_updated": "Vòstra adreça IP es estada actualizada pel domeni DynDNS", - "dyndns_key_generating": "La clau DNS es a se generar, pòt trigar una estona...", + "dyndns_key_generating": "La clau DNS es a se generar, pòt trigar una estona…", "dyndns_key_not_found": "Clau DNS introbabla pel domeni", "dyndns_no_domain_registered": "Cap de domeni pas enregistrat amb DynDNS", "dyndns_registered": "Lo domeni DynDNS es enregistrat", @@ -313,7 +313,7 @@ "service_conf_would_be_updated": "La configuracion del servici « {service} » seriá estada actualizada", "service_description_avahi-daemon": "permet d’aténher vòstre servidor via yunohost.local sus vòstre ret local", "service_description_dnsmasq": "gerís la resolucion dels noms de domeni (DNS)", - "updating_apt_cache": "Actualizacion de la lista dels paquets disponibles...", + "updating_apt_cache": "Actualizacion de la lista dels paquets disponibles…", "service_conf_file_backed_up": "Lo fichièr de configuracion « {conf} » es salvagardat dins « {backup} »", "service_conf_file_copy_failed": "Còpia impossibla del nòu fichièr de configuracion « {new} » cap a « {conf} »", "server_reboot_confirm": "Lo servidor es per reaviar sul pic, o volètz vertadièrament ? {answers:s}", @@ -372,7 +372,7 @@ "migrate_tsig_start": "L’algorisme de generacion de claus es pas pro securizat per la signatura TSIG del domeni « {domain} », lançament de la migracion cap a hmac-sha512 que’s mai securizat", "migration_description_0001_change_cert_group_to_sslcert": "Càmbia las permissions de grop dels certificats de « metronome » per « ssl-cert »", "migration_0003_restoring_origin_nginx_conf": "Vòstre fichièr /etc/nginx/nginx.conf es estat modificat manualament. La migracion reïnicializarà d’en primièr son estat origina… Lo fichièr precedent serà disponible coma {backup_dest}.", - "migration_0003_still_on_jessie_after_main_upgrade": "Quicòm a trucat pendent la mesa a nivèl màger : lo sistèma es encara jos Jessie ?!? Per trobar lo problèma, agachatz {log} …", + "migration_0003_still_on_jessie_after_main_upgrade": "Quicòm a trucat pendent la mesa a nivèl màger : lo sistèma es encara jos Jessie ?!? Per trobar lo problèma, agachatz {log}…", "migration_0003_general_warning": "Notatz qu’aquesta migracion es una operacion delicata. Encara que la còla YunoHost aguèsse fach çò melhor per la tornar legir e provar, la migracion poiriá copar de parts del sistèma o de las aplicacions.\n\nEn consequéncia, vos recomandam :\n· · · · - de lançar una salvagarda de vòstras donadas o aplicacions criticas. Mai d’informacions a https://yunohost.org/backup ;\n· · · · - d’èsser pacient aprèp aver lançat la migracion : segon vòstra connexion Internet e material, pòt trigar qualques oras per que tot siá mes al nivèl.\n\nEn mai, lo pòrt per SMTP, utilizat pels clients de corrièls extèrns (coma Thunderbird o K9-Mail per exemple) foguèt cambiat de 465 (SSL/TLS) per 587 (STARTTLS). L’ancian pòrt 465 serà automaticament tampat e lo nòu pòrt 587 serà dobèrt dins lo parafuòc. Vosautres e vòstres utilizaires *auretz* d’adaptar la configuracion de vòstre client de corrièl segon aqueles cambiaments !", "migration_0003_problematic_apps_warning": "Notatz que las aplicacions seguentas, saique problematicas, son estadas desactivadas. Semblan d’aver estadas installadas d’una lista d’aplicacions o que son pas marcadas coma «working ». En consequéncia, podèm pas assegurar que tendràn de foncionar aprèp la mesa a nivèl : {problematic_apps}", "migrations_bad_value_for_target": "Nombre invalid pel paramètre « target », los numèros de migracion son 0 o {}", @@ -457,14 +457,34 @@ "service_description_php7.0-fpm": "executa d’aplicacions escrichas en PHP amb nginx", "users_available": "Lista dels utilizaires disponibles :", "good_practices_about_admin_password": "Sètz per definir un nòu senhal per l’administracion. Lo senhal deu almens conténer 8 caractèrs - encara que siá de bon far d’utilizar un senhal mai long qu’aquò (ex. una passafrasa) e/o d’utilizar mantun tipes de caractèrs (majuscula, minuscula, nombre e caractèrs especials).", - "good_practices_about_user_password": "Sètz per definir un nòu senhal d’utilizaire. Lo senhal deu almens conténer 8 caractèrs - encara que siá de bon far d’utilizar un senhal mai long qu’aquò (ex. una passafrasa) e/o d’utilizar mantun tipes de caractèrs (majuscula, minuscula, nombre e caractèrs especials).", + "good_practices_about_user_password": "Sètz a mand de definir un nòu senhal d’utilizaire. Lo nòu senhal deu conténer almens 8 caractèrs, es de bon far d’utilizar un senhal mai long (es a dire una frasa de senhal) e/o utilizar mantuns tipes de caractèrs (majusculas, minusculas, nombres e caractèrs especials).", "migration_description_0006_sync_admin_and_root_passwords": "Sincronizar los senhals admin e root", "migration_0006_disclaimer": "Ara YunoHost s’espèra que los senhals admin e root sián sincronizats. En lançant aquesta migracion, vòstre senhal root serà remplaçat pel senhal admin.", "migration_0006_done": "Lo senhal root es estat remplaçat pel senhal admin.", - "password_listed": "Aqueste senhal fa part dels senhals mai utilizats del monde. Volgatz ben ne causir un mai unic.", + "password_listed": "Aqueste senhal es un dels mai utilizats al monde. Se vos plai utilizatz-ne un mai unic.", "password_too_simple_1": "Lo senhal deu conténer almens 8 caractèrs", - "password_too_simple_2": "Lo senhal deu conténer almens 8 caractèrs amb de nombres, de majusculas e de minusculas", - "password_too_simple_3": "Lo senhal deu conténer almens 8 caractèrs amb de nombres, de majusculas, de minusculas e de caractèrs especials", - "password_too_simple_4": "Lo senhal deu conténer almens 12 caractèrs amb de nombres, de majusculas, de minusculas e de caractèrs especials", - "root_password_desynchronized": "Lo senhal de l’administrator es estat cambiat, mas YunoHost a pas pogut l’espandir al senhal root !" + "password_too_simple_2": "Lo senhal deu conténer almens 8 caractèrs e numbres, majusculas e minusculas", + "password_too_simple_3": "Lo senhal deu conténer almens 8 caractèrs e nombres, majusculas e minusculas e caractèrs especials", + "password_too_simple_4": "Lo senhal deu conténer almens 12 caractèrs, de nombre, majusculas, minisculas e caractèrs specials", + "root_password_desynchronized": "Lo senhal de l’administrator es estat cambiat, mas YunoHost a pas pogut l’espandir al senhal root !", + "aborting": "Interrupcion.", + "app_not_upgraded": "Las aplicacions seguentas son pas estadas actualizadas : {apps}", + "app_start_install": "Installacion de l’aplicacion {app}…", + "app_start_remove": "Supression de l’aplicacion {app}…", + "app_start_backup": "Recuperacion dels fichièrs de salvagardar per {app}…", + "app_start_restore": "Restauracion de l’aplicacion {app}…", + "app_upgrade_several_apps": "Las aplicacions seguentas seràn mesas a jorn : {apps}", + "ask_new_domain": "Nòu domeni", + "ask_new_path": "Nòu camin", + "backup_actually_backuping": "Creacion d’un archiu de seguretat a partir dels fichièrs recuperats…", + "backup_mount_archive_for_restore": "Preparacion de l’archiu per restauracion…", + "dyndns_could_not_check_available": "Verificacion impossibla de la disponibilitat de {domain:s} sus {provider:s}.", + "file_does_not_exist": "Lo camin {path:s} existís pas.", + "global_settings_setting_security_password_admin_strength": "Fòrça del senhal administrator", + "global_settings_setting_security_password_user_strength": "Fòrça del senhal utilizaire", + "migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuracion SSH serà gerada per YunoHost (etapa 1, automatica)", + "migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Daissar YunoHost gerir la configuracion SSH (etapa 2, manuala)", + "migration_0007_cancelled": "YunoHost a pas reüssit a melhorar lo biais de gerir la configuracion SSH.", + "root_password_replaced_by_admin_password": "Lo senhal root es estat remplaçat pel senhal administrator.", + "service_restarted": "Lo servici '{service:s}' es estat reaviat" } From 70ebf9b121778bda831cbb586cfe569394ae6ea2 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Thu, 21 Mar 2019 10:07:28 +0000 Subject: [PATCH 417/721] Translated using Weblate (Catalan) Currently translated at 31.7% (160 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index 44d473d91..dd01ddebb 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -112,5 +112,51 @@ "password_too_simple_1": "La contrasenya ha de tenir un mínim de 8 caràcters", "password_too_simple_2": "La contrasenya ha de tenir un mínim de 8 caràcters i ha de contenir dígits, majúscules i minúscules", "password_too_simple_3": "La contrasenya ha de tenir un mínim de 8 caràcters i tenir dígits, majúscules, minúscules i caràcters especials", - "password_too_simple_4": "La contrasenya ha de tenir un mínim de 12 caràcters i tenir dígits, majúscules, minúscules i caràcters especials" + "password_too_simple_4": "La contrasenya ha de tenir un mínim de 12 caràcters i tenir dígits, majúscules, minúscules i caràcters especials", + "backup_no_uncompress_archive_dir": "El directori de l'arxiu descomprimit no existeix", + "backup_nothings_done": "No hi ha res a guardar", + "backup_output_directory_forbidden": "Directori de sortida no permès. Les còpies de seguretat no es poden crear ni dins els directoris /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ni dins els subdirectoris /home/yunohost.backup/archives", + "backup_output_directory_not_empty": "El directori de sortida no està buit", + "backup_output_directory_required": "Heu d'especificar un directori de sortida per la còpia de seguretat", + "backup_output_symlink_dir_broken": "Teniu un enllaç simbòlic trencat en lloc del directori dels arxius '{path:s}'. Pot ser teniu una configuració per la còpia de seguretat específica en un altre sistema de fitxers, si és el cas segurament heu oblidat muntar o connectar el disc dur o la clau USB.", + "backup_php5_to_php7_migration_may_fail": "No s'ha pogut convertir l'arxiu per suportar php7, la restauració de les vostres aplicacions pot fallar (raó: {error:s})", + "backup_running_hooks": "Executant els scripts de la còpia de seguretat…", + "backup_system_part_failed": "No s'ha pogut fer la còpia de seguretat de la part \"{part:s}\" del sistema", + "backup_unable_to_organize_files": "No s'han pogut organitzar els fitxers dins de l'arxiu amb el mètode ràpid", + "backup_with_no_backup_script_for_app": "L'aplicació {app:s} no té un script de còpia de seguretat. Serà ignorat.", + "backup_with_no_restore_script_for_app": "L'aplicació {app:s} no té un script de restauració, no podreu restaurar automàticament la còpia de seguretat d'aquesta aplicació.", + "certmanager_acme_not_configured_for_domain": "El certificat pel domini {domain:s} sembla que no està instal·lat correctament. Si us plau executeu primer cert-install per aquest domini.", + "certmanager_attempt_to_renew_nonLE_cert": "El certificat pel domini {domain:s} no ha estat emès per Let's Encrypt. No es pot renovar automàticament!", + "certmanager_attempt_to_renew_valid_cert": "El certificat pel domini {domain:s} està a punt de caducar! Utilitzeu --force per ometre", + "certmanager_attempt_to_replace_valid_cert": "Esteu intentant sobreescriure un certificat correcte i vàlid pel domini {domain:s}! (Utilitzeu --force per ometre)", + "certmanager_cannot_read_cert": "S'ha produït un error al intentar obrir el certificat actual pel domini {domain:s} (arxiu: {file:s}), raó: {reason:s}", + "certmanager_cert_install_success": "S'ha instal·lat correctament un certificat Let's Encrypt pel domini {domain:s}!", + "certmanager_cert_install_success_selfsigned": "S'ha instal·lat correctament un certificat auto-signat pel domini {domain:s}!", + "certmanager_cert_renew_success": "S'ha renovat correctament el certificat Let's Encrypt pel domini {domain:s}!", + "certmanager_cert_signing_failed": "No s'ha pogut firmar el nou certificat", + "certmanager_certificate_fetching_or_enabling_failed": "Sembla que l'activació del nou certificat per {domain:s} ha fallat…", + "certmanager_conflicting_nginx_file": "No s'ha pogut preparar el domini per al desafiament ACME: l'arxiu de configuració nginx {filepath:s} entra en conflicte i s'ha d'eliminar primer", + "certmanager_couldnt_fetch_intermediate_cert": "S'ha exhaurit el temps d'esperar al intentar recollir el certificat intermedi des de Let's Encrypt. La instal·lació/renovació del certificat s'ha cancel·lat - torneu a intentar-ho més tard.", + "certmanager_domain_cert_not_selfsigned": "El certificat pel domini {domain:s} no és auto-signat Esteu segur de voler canviar-lo? (Utilitzeu --force per fer-ho)", + "certmanager_domain_dns_ip_differs_from_public_ip": "El registre DNS \"A\" pel domini {domain:s} és diferent a l'adreça IP d'aquest servidor. Si heu modificat recentment el registre A, si us plau espereu a que es propagui (hi ha eines per verificar la propagació disponibles a internet). (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)", + "certmanager_domain_http_not_working": "Sembla que el domini {domain:s} no és accessible via HTTP. Si us plau verifiqueu que les configuracions DNS i nginx siguin correctes", + "certmanager_domain_not_resolved_locally": "El domini {domain:s} no es pot resoldre dins del vostre servidor YunoHost. Això pot passar si heu modificat recentment el registre DNS. Si és així, si us plau espereu unes hores per a que es propagui. Si el problema continua, considereu afegir {domain:s} a /etc/hosts. (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)", + "certmanager_domain_unknown": "Domini desconegut {domain:s}", + "certmanager_error_no_A_record": "No s'ha trobat cap registre DNS \"A\" per {domain:s}. Heu de fer que el vostre nom de domini apunti cap a la vostra màquina per tal de poder instal·lar un certificat Let's Encrypt! (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)", + "certmanager_hit_rate_limit": "S'han emès massa certificats recentment per aquest mateix conjunt de dominis {domain:s}. Si us plau torneu-ho a intentar més tard. Consulteu https://letsencrypt.org/docs/rate-limits/ per obtenir més detalls", + "certmanager_http_check_timeout": "S'ha exhaurit el temps d'espera quan el servidor ha intentat contactar amb ell mateix via HTTP utilitzant la seva adreça IP pública (domini domain:s} amb IP {ip:s}). Pot ser degut a hairpinning o a que el talla focs/router al que està connectat el servidor estan mal configurats.", + "certmanager_no_cert_file": "No s'ha pogut llegir l'arxiu del certificat pel domini {domain:s} (fitxer: {file:s})", + "certmanager_self_ca_conf_file_not_found": "No s'ha trobat el fitxer de configuració per l'autoritat del certificat auto-signat (fitxer: {file:s})", + "certmanager_unable_to_parse_self_CA_name": "No s'ha pogut analitzar el nom de l'autoritat del certificat auto-signat (fitxer: {file:s})", + "confirm_app_install_warning": "Atenció: aquesta aplicació funciona però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers:s}] ", + "confirm_app_install_danger": "ATENCIÓ! Aquesta aplicació encara és experimental (si no és que no funciona directament) i és probable que trenqui el sistema! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. Esteu segurs de voler córrer aquest risc? [{answers:s}] ", + "confirm_app_install_thirdparty": "ATENCIÓ! La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. Faci-ho sota la seva responsabilitat.No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. Esteu segurs de voler córrer aquest risc? [{answers:s}] ", + "custom_app_url_required": "Heu de especificar una URL per actualitzar la vostra aplicació personalitzada {app:s}", + "custom_appslist_name_required": "Heu d'especificar un nom per la vostra llista d'aplicacions personalitzada", + "diagnosis_debian_version_error": "No s'ha pogut obtenir la versió Debian: {error}", + "diagnosis_kernel_version_error": "No s'ha pogut obtenir la versió del nucli: {error}", + "diagnosis_monitor_disk_error": "No es poden monitorar els discs: {error}", + "diagnosis_monitor_network_error": "No es pot monitorar la xarxa: {error}", + "diagnosis_monitor_system_error": "No es pot monitorar el sistema: {error}", + "diagnosis_no_apps": "No hi ha cap aplicació instal·lada" } From ce596a401aebe364e98e97543b240280b450a537 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Holcroft Date: Fri, 22 Mar 2019 05:48:11 +0000 Subject: [PATCH 418/721] Translated using Weblate (French) Currently translated at 97.0% (489 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 58c25d3ad..1948751da 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -471,7 +471,7 @@ "service_description_php7.0-fpm": "exécute des applications écrites en PHP avec Nginx", "users_available": "Liste des utilisateurs disponibles :", "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", - "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères — bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou d'utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", + "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères — bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", "migration_0006_disclaimer": "YunoHost s’attendra à ce que les mots de passe admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.", "migration_0006_done": "Votre mot de passe root a été remplacé par celui de votre adminitrateur.", @@ -481,7 +481,7 @@ "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", "root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager au mot de passe root !", - "aborting": "Interruption de la procédure.", + "aborting": "Interruption.", "app_not_upgraded": "Les applications suivantes n'ont pas été mises à jour : {apps}", "app_start_install": "Installation de l'application {app} …", "app_start_remove": "Suppression de l'application {app} …", From fc43640ae2fa660b2663870741926f6a47e80b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Fri, 22 Mar 2019 20:37:49 +0000 Subject: [PATCH 419/721] Translated using Weblate (French) Currently translated at 98.8% (498 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 1948751da..d8d3edbb1 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -120,7 +120,7 @@ "ldap_initialized": "L’annuaire LDAP a été initialisé", "license_undefined": "indéfinie", "mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'", - "mail_domain_unknown": "Le domaine '{domain:s}' est inconnu pour cette adresse de courriel", + "mail_domain_unknown": "Le domaine « {domain:s} » du courriel est inconnu", "mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'", "maindomain_change_failed": "Impossible de modifier le domaine principal", "maindomain_changed": "Le domaine principal a été modifié", @@ -153,7 +153,7 @@ "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 avec un maximum de 30 caractères, et composé uniquement de caractères alphanumériques et de tirets tels que -_.", + "pattern_backup_archive_name": "Doit être un nom de fichier valide avec un maximum de 30 caractères, et composé de caractères alphanumériques et -_. uniquement", "pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.fr)", "pattern_email": "Doit être une adresse de courriel valide (ex. : pseudo@domaine.fr)", "pattern_firstname": "Doit être un prénom valide", @@ -264,7 +264,7 @@ "certmanager_cert_renew_success": "Renouvellement avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !", "certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés", "certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué", - "certmanager_no_cert_file": "Impossible de lire le fichier de/du certificat pour le domaine {domain:s} (fichier : {file:s})", + "certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})", "certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration Nginx {filepath:s} est en conflit et doit être préalablement retiré", "certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations", "ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin", @@ -297,7 +297,7 @@ "app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante :\n{apps:s}", "app_already_up_to_date": "{app:s} est déjà à jour", "invalid_url_format": "Format d’URL non valide", - "global_settings_bad_choice_for_enum": "La valeur du paramètre {setting:s} est incorrecte. Reçu {received_type:s} alors que {expected_type:s} était attendu", + "global_settings_bad_choice_for_enum": "Valeur du paramètre {setting:s} incorrecte. Reçu : {received_type:s}, mais les valeurs possibles sont : {expected_type:s}", "global_settings_bad_type_for_setting": "Le type du paramètre {setting:s} est incorrect. Reçu {received_type:s} alors que {expected_type:s} était attendu", "global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations car : {reason:s}", "global_settings_cant_serialize_setings": "Échec de sérialisation des données de configurations, cause : {reason:s}", @@ -315,7 +315,7 @@ "backup_abstract_method": "Cette méthode de sauvegarde n’a pas encore été implémentée", "backup_applying_method_tar": "Création de l’archive tar de la sauvegarde …", "backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …", - "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le référentiel/répertoire borg-backup …", + "backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup…", "backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …", "backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde", "backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué", @@ -347,7 +347,7 @@ "restore_system_part_failed": "Impossible de restaurer la partie '{part:s}' du système", "backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.", "domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre fournisseur/registrar DNS avec cette recommandation.", - "domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost. Soit YunoHost n’est pas correctement connecté à internet, soit le serveur de dynette est en panne ou ne répond pas. Erreur : {error}", + "domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost. Soit YunoHost n’est pas correctement connecté à internet, soit le serveur de dynette est en panne. Erreur : {error}", "migrations_backward": "Migration en arrière.", "migrations_bad_value_for_target": "Nombre invalide pour le paramètre target, les numéros de migration sont 0 ou {}", "migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migrations avec le chemin %s", @@ -370,7 +370,7 @@ "dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.", "app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine {domain} car il est déjà utilisé par l'application '{other_app}'", "app_upgrade_app_name": "Mise à jour de l’application {app} …", - "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives '{path:s}'. Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clef USB.", + "backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clé USB.", "migrate_tsig_end": "La migration à hmac-sha512 est terminée", "migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}", "migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers hmac-sha512 qui est plus sécurisé", @@ -380,7 +380,7 @@ "migrate_tsig_wait_4": "30 secondes …", "migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire !", "app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' est obsolète ! Utilisez 'app register-url' en remplacement !", - "migration_description_0001_change_cert_group_to_sslcert": "Changement des permissions de groupe des certificats de 'metronome' à 'ssl-cert'", + "migration_description_0001_change_cert_group_to_sslcert": "Changement des permissions de groupe des certificats de « metronome » à « ssl-cert »", "migration_description_0002_migrate_to_tsig_sha256": "Amélioration de la sécurité de DynDNS TSIG en utilisant SHA512 au lieu de MD5", "migration_description_0003_migrate_to_stretch": "Mise à niveau du système vers Debian Stretch et YunoHost 3.0", "migration_0003_backward_impossible": "La migration Stretch n’est pas réversible.", @@ -392,7 +392,7 @@ "migration_0003_yunohost_upgrade": "Démarrage de la mise à niveau du paquet YunoHost. La migration se terminera, mais la mise à jour réelle aura lieu immédiatement après. Une fois cette opération terminée, vous pourriez avoir à vous reconnecter à l’administration via le panel web.", "migration_0003_not_jessie": "La distribution Debian actuelle n’est pas Jessie !", "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", - "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Jessie !? Pour en savoir plus et investiguer sur ce problème, veuillez regarder les journaux {log}:s .", + "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Jessie !? Pour investiguer sur problème, veuillez regarder les journaux {log}:s…", "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence !", "migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme 'fonctionnelles'/'working'. En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", @@ -426,7 +426,7 @@ "backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge php7, vos applications php pourraient ne pas être restaurées (reason: {error:s})", "log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le journal historisé de cette opération en utilisant la commande 'yunohost log display {name} --share'", "log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'", - "log_operation_unit_unclosed_properly": "L’opération ne s’est pas fermée/terminée correctement", + "log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement", "log_app_addaccess": "Ajouter l’accès à '{}'", "log_app_removeaccess": "Enlever l’accès à '{}'", "log_app_clearaccess": "Retirer tous les accès à '{}'", @@ -462,7 +462,7 @@ "log_tools_shutdown": "Éteindre votre serveur", "log_tools_reboot": "Redémarrer votre serveur", "mail_unavailable": "Cette adresse de courriel est réservée et doit être automatiquement attribuée au tout premier utilisateur", - "migration_description_0004_php5_to_php7_pools": "Reconfigurez le pool PHP pour utiliser PHP 7 au lieu de PHP 5", + "migration_description_0004_php5_to_php7_pools": "Reconfiguration des pools PHP pour utiliser PHP 7 au lieu de PHP 5", "migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6", "migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !", "migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système :( …", From 2d4785ec59099e56c566c70a6bed453d4028fda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Sat, 23 Mar 2019 13:25:50 +0000 Subject: [PATCH 420/721] Translated using Weblate (French) Currently translated at 99.8% (503 of 504 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index d8d3edbb1..1a8399454 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -309,7 +309,7 @@ "global_settings_setting_example_string": "Exemple d’option de type chaîne", "global_settings_setting_example_enum": "Exemple d’option de type énumération", "global_settings_unknown_type": "Situation inattendue : la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n'est pas pris en charge par le système.", - "global_settings_unknown_setting_from_settings_file": "Clef inconnue dans les paramètres : '{setting_key:s}', rejet de cette clef et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", + "global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key:s}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json", "service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.", "service_conf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par le service {service} mais a été conservé.", "backup_abstract_method": "Cette méthode de sauvegarde n’a pas encore été implémentée", @@ -359,7 +359,7 @@ "migrations_no_migrations_to_run": "Aucune migration à lancer", "migrations_show_currently_running_migration": "Application de la migration {number} {name} …", "migrations_show_last_migration": "La dernière migration appliquée est {}", - "migrations_skip_migration": "Ignorer et passer la migration {number} {name} …", + "migrations_skip_migration": "Ignorer et passer la migration {number} {name}…", "server_shutdown": "Le serveur va éteindre", "server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]", "server_reboot": "Le serveur va redémarrer", @@ -394,7 +394,7 @@ "migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.", "migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Jessie !? Pour investiguer sur problème, veuillez regarder les journaux {log}:s…", "migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence !", - "migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme 'fonctionnelles'/'working'. En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", + "migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme « fonctionnelles ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}", "migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}", "migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.", "migrations_to_be_ran_manually": "La migration {number} {name} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.", @@ -473,7 +473,7 @@ "good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères — bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).", "migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root", - "migration_0006_disclaimer": "YunoHost s’attendra à ce que les mots de passe admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.", + "migration_0006_disclaimer": "YunoHost s’attendra désormais à ce que les mots de passe admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.", "migration_0006_done": "Votre mot de passe root a été remplacé par celui de votre adminitrateur.", "password_listed": "Ce mot de passe est l'un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d'un peu plus singulier.", "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", From 992e8e91b63daf30bc0b288e89c5542169f9b758 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Thu, 28 Mar 2019 23:25:26 +0000 Subject: [PATCH 421/721] Translated using Weblate (Catalan) Currently translated at 33.5% (170 of 507 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index dd01ddebb..9023b8c75 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -158,5 +158,15 @@ "diagnosis_monitor_disk_error": "No es poden monitorar els discs: {error}", "diagnosis_monitor_network_error": "No es pot monitorar la xarxa: {error}", "diagnosis_monitor_system_error": "No es pot monitorar el sistema: {error}", - "diagnosis_no_apps": "No hi ha cap aplicació instal·lada" + "diagnosis_no_apps": "No hi ha cap aplicació instal·lada", + "admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters", + "dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/apt (els gestors de paquets del sistema) sembla estar mal configurat... Podeu intentar solucionar-ho connectant-vos per ssh i executant \"sudo dpkg --configure -a\".", + "dnsmasq_isnt_installed": "sembla que dnsmasq no està instal·lat, executeu \"apt-get remove bind9 && apt-get install dnsmasq\"", + "domain_cannot_remove_main": "No es pot eliminar el domini principal. S'ha d'establir un nou domini primer", + "domain_cert_gen_failed": "No s'ha pogut generar el certificat", + "domain_created": "S'ha creat el domini", + "domain_creation_failed": "No s'ha pogut crear el domini", + "domain_deleted": "S'ha eliminat el domini", + "domain_deletion_failed": "No s'ha pogut eliminar el domini", + "domain_exists": "El domini ja existeix" } From b6cff68d73eafffbf985cd213cd2318b720823d2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Apr 2019 18:15:16 +0200 Subject: [PATCH 422/721] [fix] Do not miserably crash if status.json can't be decoded --- src/yunohost/app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3d33ab8ae..38df51ea0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1773,12 +1773,18 @@ def _get_app_status(app_id, format_date=False): raise YunohostError('app_unknown') status = {} + regen_status = True try: with open(app_setting_path + '/status.json') as f: status = json.loads(str(f.read())) + regen_status = False except IOError: logger.debug("status file not found for '%s'", app_id, exc_info=1) + except Exception as e: + logger.warning("could not open or decode %s : %s ... regenerating.", app_setting_path + '/status.json', str(e)) + + if regen_status: # Create app status status = { 'installed_at': app_setting(app_id, 'install_time'), From 889eb0caa158b9c951fd34909091f37d1ce06f55 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 2 Apr 2019 20:47:03 +0200 Subject: [PATCH 423/721] [fix] Fix the dbus interface to get info for services, because this was not working when service is disabled --- src/yunohost/service.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 61274aaac..896090228 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -325,7 +325,7 @@ def service_status(names=[]): result[name] = { 'status': str(status.get("SubState", "unknown")), - 'loaded': "enabled" if str(status.get("LoadState", "unknown")) == "loaded" else str(status.get("LoadState", "unknown")), + 'loaded': str(status.get("UnitFileState", "unknown")), 'active': str(status.get("ActiveState", "unknown")), 'description': description, 'service_file_path': str(status.get("FragmentPath", "unknown")), @@ -343,26 +343,25 @@ def service_status(names=[]): def _get_service_information_from_systemd(service): "this is the equivalent of 'systemctl status $service'" import dbus - from dbus.exceptions import DBusException d = dbus.SystemBus() systemd = d.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') manager = dbus.Interface(systemd, 'org.freedesktop.systemd1.Manager') - try: - service_path = manager.GetUnit(service + ".service") - except DBusException as exception: - if exception.get_dbus_name() == 'org.freedesktop.systemd1.NoSuchUnit': - return None - raise - - service_proxy = d.get_object('org.freedesktop.systemd1', service_path) - - # unit_proxy = dbus.Interface(service_proxy, 'org.freedesktop.systemd1.Unit',) + # c.f. https://zignar.net/2014/09/08/getting-started-with-dbus-python-systemd/ + # Very interface, much intuitive, wow + service_unit = manager.LoadUnit(service + '.service') + service_proxy = d.get_object('org.freedesktop.systemd1', str(service_unit)) properties_interface = dbus.Interface(service_proxy, 'org.freedesktop.DBus.Properties') - return properties_interface.GetAll('org.freedesktop.systemd1.Unit') + properties = properties_interface.GetAll('org.freedesktop.systemd1.Unit') + + if properties.get("LoadState", "not-found") == "not-found": + # Service doesn't really exist + return None + else: + return properties def service_log(name, number=50): From 15cfe22b3c835e313a44ea44d07105ba9e3a0b2d Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 3 Apr 2019 00:25:50 +0200 Subject: [PATCH 424/721] Add size of apps in backup_info result --- src/yunohost/backup.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 6f969327b..f10b112ac 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -2272,6 +2272,14 @@ def backup_info(name, with_details=False, human_readable=False): if "hooks" in info.keys(): system_key = "hooks" + + if "size_details" in info.keys(): + for name, key_info in info["apps"].items(): + if name in info["size_details"]["apps"].keys(): + key_info["size"] = info["size_details"]["apps"][name] + else: + key_info["size"] = 0 + result["apps"] = info["apps"] result["system"] = info[system_key] return result From e7ec6d968078207755572bbcb80df59e8454e90c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 3 Apr 2019 02:21:31 +0200 Subject: [PATCH 425/721] Update changelog for 3.5.1 --- debian/changelog | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/debian/changelog b/debian/changelog index 444d797e1..831b21ef5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,22 @@ +yunohost (3.5.1) testing; urgency=low + + - [fix] Fix the dbus interface to get info for services (#698) + - [mod] Use ask key for display_text instead and support i18n (#697) + - [fix] Rework tools update (#695) + - [enh] Nginx conf tweaks for theme (#689) + - [fix] Fix argument escaping in getopts (#685, #683) + - [enh] Support php versions in ynh_add_fpm_config (#674) + - [enh] Check that required services are up before running app install and upgrade (#670) + - [doc] Add min version for all helpers (#664) + - [enh] Add a setting to control compatibility/security tradeoff for nginx and ssh configurations (#640) + - [enh] Hooks to allow apps to extend the recommended DNS configuration (#517) + - Misc technical fixes / improvements (0bd781b, fad3edf, 1268872, 847ceca, 26e77b7, b6cff68) + - [i18n] Update translation for French, Catalan, Esperanto, Occitan + + Thanks to all contributors: Aleks, Bram, Gabriel Corona, Jibec, Josue, Maniack C, Mélanie C., Quentí, Romuald du Song, ljf, ppr, Xaloc ! <3 + + -- Alexandre Aubin Wed, 03 Apr 2019 02:13:00 +0000 + yunohost (3.5.0.2) testing; urgency=low - [fix] Make sure that `ynh_system_user_delete` also deletes the group (#680) From bf3ffc3e7d8d84c8e35a54fcdfb78b45c553dd8d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 3 Apr 2019 12:22:47 +0200 Subject: [PATCH 426/721] [fix] service_status returns different type of data if you one or multiple services --- src/yunohost/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 38df51ea0..97210d60c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2610,10 +2610,10 @@ def _check_services_status_for_app(services): # (added by apps) and because those are the most popular # services service_filter = ["nginx", "php7.0-fpm", "mysql", "postfix"] - services = [s for s in services if s in service_filter] + services = [str(s) for s in services if s in service_filter] # List services currently down and raise an exception if any are found - faulty_services = [s for s, infos in service_status(services).items() if infos["active"] != "active"] + faulty_services = [s for s in services if service_status(s)["active"] != "active"] if faulty_services: raise YunohostError('app_action_cannot_be_ran_because_required_services_down', services=', '.join(faulty_services)) From 5c4bf1ed2905e0a69ca11ba0b13407e8493815b2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 3 Apr 2019 13:02:15 +0200 Subject: [PATCH 427/721] [fix] nginx diagnosis when there's an error throwing a huge useless traceback. Use Popen instead to display the real error --- src/yunohost/tools.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 635399801..0fb097805 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -720,12 +720,13 @@ def tools_diagnosis(auth, private=False): } # nginx -t - try: - diagnosis['nginx'] = check_output("nginx -t").strip().split("\n") - except Exception as e: - import traceback - traceback.print_exc() - logger.warning("Unable to check 'nginx -t', exception: %s" % e) + p = subprocess.Popen("nginx -t".split(), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + out, _ = p.communicate() + diagnosis["nginx"] = out.strip().split("\n") + if p.returncode != 0: + logger.error(out) # Services status services = service_status() From 1f1173ad0c699904e79903362f0ca51978ba80f0 Mon Sep 17 00:00:00 2001 From: yalh76 Date: Wed, 3 Apr 2019 13:46:37 +0200 Subject: [PATCH 428/721] Use YNH_APP_INSTANCE_NAME instead of YNH_APP_ID when using YNH_APP_ID, if you install two time the same application, when removing one of them, nodejs is uninstalled --- data/helpers.d/nodejs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index b51bcd7c3..6833b7593 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -123,7 +123,7 @@ ynh_install_nodejs () { fi # Store the ID of this app and the version of node requested for it - echo "$YNH_APP_ID:$nodejs_version" | tee --append "$n_install_dir/ynh_app_version" + echo "$YNH_APP_INSTANCE_NAME:$nodejs_version" | tee --append "$n_install_dir/ynh_app_version" # Store nodejs_version into the config of this app ynh_app_setting_set --app=$app --key=nodejs_version --value=$nodejs_version @@ -147,7 +147,7 @@ ynh_remove_nodejs () { nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) # Remove the line for this app - sed --in-place "/$YNH_APP_ID:$nodejs_version/d" "$n_install_dir/ynh_app_version" + sed --in-place "/$YNH_APP_INSTANCE_NAME:$nodejs_version/d" "$n_install_dir/ynh_app_version" # If no other app uses this version of nodejs, remove it. if ! grep --quiet "$nodejs_version" "$n_install_dir/ynh_app_version" From 69d2ad9a8ad8c9fbd95b7a0bb87b19656b12793d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 3 Apr 2019 16:56:29 +0200 Subject: [PATCH 429/721] Fix loaded status for sysv services --- src/yunohost/service.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 896090228..b139c0b6d 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -31,6 +31,7 @@ import subprocess import shutil import hashlib +from glob import glob from difflib import unified_diff from datetime import datetime @@ -330,6 +331,12 @@ def service_status(names=[]): 'description': description, 'service_file_path': str(status.get("FragmentPath", "unknown")), } + + # Fun stuff™ : to obtain the enabled/disabled status for sysv services, + # gotta do this ... cf code of /lib/systemd/systemd-sysv-install + if result[name]["loaded"] == "generated": + result[name]["loaded"] = "enabled" if glob("/etc/rc[S5].d/S??"+name) else "disabled" + if "ActiveEnterTimestamp" in status: result[name]['active_at'] = datetime.utcfromtimestamp(status["ActiveEnterTimestamp"] / 1000000) else: From c262313477aa0a7494f7b2bdcdeeef69ab85f807 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 3 Apr 2019 17:28:20 +0200 Subject: [PATCH 430/721] Update changelog for 3.5.1.1 --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 831b21ef5..2d42719f6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +yunohost (3.5.1.1) testing; urgency=low + + - [fix] enabled/disabled status for sysv services + - [fix] Nodejs helpers : use YNH_APP_INSTANCE_NAME instead of YNH_APP_ID (#700) + - [fix] nginx diagnosis when there's an error throwing a huge useless traceback. Use Popen instead to display the real error + - [fix] service_status returns different type of data if you ask for one or multiple services + + -- Alexandre Aubin Wed, 03 Apr 2019 17:28:00 +0000 + yunohost (3.5.1) testing; urgency=low - [fix] Fix the dbus interface to get info for services (#698) From 1a1fc0bdff52fbc9d77ed01c8148e818d358749b Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 4 Apr 2019 12:28:00 +0200 Subject: [PATCH 431/721] Add connection_upgrade --- data/templates/nginx/server.tpl.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index fa8b6586b..14cb52abc 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -1,3 +1,8 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + server { listen 80; listen [::]:80; From e9d399f7cd92ba50ea19b7da1fcc25997ecb650b Mon Sep 17 00:00:00 2001 From: Kayou Date: Thu, 4 Apr 2019 12:29:23 +0200 Subject: [PATCH 432/721] Format --- data/templates/nginx/server.tpl.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/templates/nginx/server.tpl.conf b/data/templates/nginx/server.tpl.conf index 14cb52abc..4a5e91557 100644 --- a/data/templates/nginx/server.tpl.conf +++ b/data/templates/nginx/server.tpl.conf @@ -1,6 +1,6 @@ map $http_upgrade $connection_upgrade { - default upgrade; - '' close; + default upgrade; + '' close; } server { From 214d1ce8fc14b2c1afb7a7852574d6853e9c5be0 Mon Sep 17 00:00:00 2001 From: Josue-T Date: Sun, 7 Apr 2019 11:27:38 +0200 Subject: [PATCH 433/721] Add support in restore script of ynh_setup_source Actually the path to the app.src file in the helper ynh_setup_source work only in install and upgrade script. Add the support for restore script. --- data/helpers.d/utils | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 5f5e61015..40e6fe045 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -166,15 +166,22 @@ ynh_setup_source () { ynh_handle_getopts_args "$@" source_id="${source_id:-app}" # If the argument is not given, source_id equals "app" + local src_file_path="$YNH_CWD/../conf/${src_id}.src" + # In case of restore script the src file is in an other path. + # So try to use the restore path if the general path point to no file. + if [[ ! -e "$src_file_path" ]]; then + src_file_path="$YNH_CWD/../settings/conf/${src_id}.src" + fi + # Load value from configuration file (see above for a small doc about this file # format) - local src_url=$(grep 'SOURCE_URL=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_sum=$(grep 'SOURCE_SUM=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_format=$(grep 'SOURCE_FORMAT=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_extract=$(grep 'SOURCE_EXTRACT=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) - local src_filename=$(grep 'SOURCE_FILENAME=' "$YNH_CWD/../conf/${source_id}.src" | cut -d= -f2-) + local src_url=$(grep 'SOURCE_URL=' "$src_file_path" | cut -d= -f2-) + local src_sum=$(grep 'SOURCE_SUM=' "$src_file_path" | cut -d= -f2-) + local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$src_file_path" | cut -d= -f2-) + local src_format=$(grep 'SOURCE_FORMAT=' "$src_file_path" | cut -d= -f2-) + local src_extract=$(grep 'SOURCE_EXTRACT=' "$src_file_path" | cut -d= -f2-) + local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$src_file_path" | cut -d= -f2-) + local src_filename=$(grep 'SOURCE_FILENAME=' "$src_file_path" | cut -d= -f2-) # Default value src_sumprg=${src_sumprg:-sha256sum} From a9761d356b8c787cbef6f0fb38025e75cb5e1ef0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 8 Apr 2019 18:59:04 +0200 Subject: [PATCH 434/721] Update script to automatically generate helper documentation --- data/helpers.d/backend | 15 ++++++------ data/helpers.d/filesystem | 39 +++++++++++++------------------- data/helpers.d/mysql | 4 ++-- data/helpers.d/network | 12 ++++++---- data/helpers.d/system | 2 +- data/helpers.d/user | 11 ++++----- doc/generate_helper_doc.py | 44 ++++++++++++++++++++++++++++-------- doc/helper_doc_template.html | 28 +++++++++++++++++++---- 8 files changed, 96 insertions(+), 59 deletions(-) diff --git a/data/helpers.d/backend b/data/helpers.d/backend index 28c91e4f6..710e6299b 100644 --- a/data/helpers.d/backend +++ b/data/helpers.d/backend @@ -3,17 +3,17 @@ # Use logrotate to manage the logfile # # usage: ynh_use_logrotate [--logfile=/log/file] [--nonappend] [--specific_user=user/group] -# | arg: -l, --logfile= - absolute path of logfile -# | arg: -n, --nonappend - (Option) Replace the config file instead of appending this new config. +# | arg: -l, --logfile - absolute path of logfile +# | arg: -n, --nonappend - (optional) Replace the config file instead of appending this new config. # | arg: -u, --specific_user : run logrotate as the specified user and group. If not specified logrotate is runned as root. # -# If no argument provided, a standard directory will be use. /var/log/${app} -# You can provide a path with the directory only or with the logfile. +# If no --logfile is provided, /var/log/${app} will be used as default. +# logfile can be just a directory, or a full path to a logfile : # /parentdir/logdir # /parentdir/logdir/logfile.log # -# It's possible to use this helper several times, each config will be added to the same logrotate config file. -# Unless you use the option --non-append +# It's possible to use this helper multiple times, each config will be added to +# the same logrotate config file. Unless you use the option --non-append # # Requires YunoHost version 2.6.4 or higher. ynh_use_logrotate () { @@ -175,8 +175,7 @@ ynh_remove_systemd_config () { # # usage: ynh_add_nginx_config "list of others variables to replace" # -# | arg: list of others variables to replace separeted by a space -# | for example : 'path_2 port_2 ...' +# | arg: list - (Optional) list of others variables to replace separated by spaces. For example : 'path_2 port_2 ...' # # This will use a template in ../conf/nginx.conf # __PATH__ by $path_url diff --git a/data/helpers.d/filesystem b/data/helpers.d/filesystem index 6fb6347a6..07614d179 100644 --- a/data/helpers.d/filesystem +++ b/data/helpers.d/filesystem @@ -15,16 +15,13 @@ CAN_BIND=${CAN_BIND:-1} # If DEST is ended by a slash it complete this path with the basename of SRC. # # usage: ynh_backup --src_path=src_path [--dest_path=dest_path] [--is_big] [--not_mandatory] -# | arg: -s, --src_path - file or directory to bind or symlink or copy. it shouldn't be in -# the backup dir. -# | arg: -d, --dest_path - destination file or directory inside the -# backup dir +# | arg: -s, --src_path - file or directory to bind or symlink or copy. it shouldn't be in the backup dir. +# | arg: -d, --dest_path - destination file or directory inside the backup dir # | arg: -b, --is_big - Indicate data are big (mail, video, image ...) # | arg: -m, --not_mandatory - Indicate that if the file is missing, the backup can ignore it. # | arg: arg - Deprecated arg # -# example: -# # Wordpress app context +# Example in the context of a wordpress app # # ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" # # => This line will be added into CSV file @@ -198,28 +195,25 @@ with open(sys.argv[1], 'r') as backup_file: # Restore a file or a directory # # Use the registered path in backup_list by ynh_backup to restore the file at -# the good place. +# the right place. # # usage: ynh_restore_file --origin_path=origin_path [--dest_path=dest_path] [--not_mandatory] -# | arg: -o, --origin_path - Path where was located the file or the directory before -# to be backuped or relative path to $YNH_CWD where it is located in the backup archive -# | arg: -d, --dest_path - Path where restore the file or the dir, if unspecified, -# the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in -# the archive, the destination will be searched into backup.csv +# | arg: -o, --origin_path - Path where was located the file or the directory before to be backuped or relative path to $YNH_CWD where it is located in the backup archive +# | arg: -d, --dest_path - Path where restore the file or the dir, if unspecified, the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in the archive, the destination will be searched into backup.csv # | arg: -m, --not_mandatory - Indicate that if the file is missing, the restore process can ignore it. # +# examples: +# ynh_restore_file "/etc/nginx/conf.d/$domain.d/$app.conf" +# # You can also use relative paths: +# ynh_restore_file "conf/nginx.conf" +# # If DEST_PATH already exists and is lighter than 500 Mo, a backup will be made in # /home/yunohost.conf/backup/. Otherwise, the existing file is removed. # -# examples: -# ynh_restore_file "/etc/nginx/conf.d/$domain.d/$app.conf" -# # if apps/wordpress/etc/nginx/conf.d/$domain.d/$app.conf exists, restore it into -# # /etc/nginx/conf.d/$domain.d/$app.conf -# # if no, search a correspondance in the csv (eg: conf/nginx.conf) and restore it into -# # /etc/nginx/conf.d/$domain.d/$app.conf -# -# # DON'T GIVE THE ARCHIVE PATH: -# ynh_restore_file "conf/nginx.conf" +# if apps/wordpress/etc/nginx/conf.d/$domain.d/$app.conf exists, restore it into +# /etc/nginx/conf.d/$domain.d/$app.conf +# if no, search for a match in the csv (eg: conf/nginx.conf) and restore it into +# /etc/nginx/conf.d/$domain.d/$app.conf # # Requires YunoHost version 2.6.4 or higher. ynh_restore_file () { @@ -348,8 +342,7 @@ ynh_store_file_checksum () { # # usage: ynh_backup_if_checksum_is_different --file=file # | arg: -f, --file - The file on which the checksum test will be perfomed. -# -# | ret: Return the name a the backup file, or nothing +# | ret: the name of a backup file, or nothing # # Requires YunoHost version 2.6.4 or higher. ynh_backup_if_checksum_is_different () { diff --git a/data/helpers.d/mysql b/data/helpers.d/mysql index 313b7a245..d7400db2d 100644 --- a/data/helpers.d/mysql +++ b/data/helpers.d/mysql @@ -186,7 +186,7 @@ ynh_mysql_drop_user() { # usage: ynh_mysql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] # | arg: -u, --db_user - Owner of the database # | arg: -n, --db_name - Name of the database -# | arg: -p, --db_pwd - Password of the database. If not given, a password will be generated +# | arg: -p, --db_pwd - Password of the database. If not provided, a password will be generated # # Requires YunoHost version 2.6.4 or higher. ynh_mysql_setup_db () { @@ -200,7 +200,7 @@ ynh_mysql_setup_db () { ynh_handle_getopts_args "$@" local new_db_pwd=$(ynh_string_random) # Generate a random password - # If $db_pwd is not given, use new_db_pwd instead for db_pwd + # If $db_pwd is not provided, use new_db_pwd instead for db_pwd db_pwd="${db_pwd:-$new_db_pwd}" ynh_mysql_create_db "$db_name" "$db_user" "$db_pwd" # Create the database diff --git a/data/helpers.d/network b/data/helpers.d/network index 8812f8f39..4dc080203 100644 --- a/data/helpers.d/network +++ b/data/helpers.d/network @@ -1,14 +1,16 @@ #!/bin/bash # Normalize the url path syntax +# # Handle the slash at the beginning of path and its absence at ending # Return a normalized url path # -# example: url_path=$(ynh_normalize_url_path $url_path) -# ynh_normalize_url_path example -> /example -# ynh_normalize_url_path /example -> /example -# ynh_normalize_url_path /example/ -> /example -# ynh_normalize_url_path / -> / +# examples: +# url_path=$(ynh_normalize_url_path $url_path) +# ynh_normalize_url_path example # -> /example +# ynh_normalize_url_path /example # -> /example +# ynh_normalize_url_path /example/ # -> /example +# ynh_normalize_url_path / # -> / # # usage: ynh_normalize_url_path --path_url=path_to_normalize # | arg: -p, --path_url - URL path to normalize before using it diff --git a/data/helpers.d/system b/data/helpers.d/system index a491b19b5..757fdf93c 100644 --- a/data/helpers.d/system +++ b/data/helpers.d/system @@ -241,7 +241,7 @@ ynh_app_package_version () { # - UPGRADE_APP if the upstream app version has changed # - UPGRADE_PACKAGE if only the YunoHost package has changed # -## It stops the current script without error if the package is up-to-date +# It stops the current script without error if the package is up-to-date # # This helper should be used to avoid an upgrade of an app, or the upstream part # of it, when it's not needed diff --git a/data/helpers.d/user b/data/helpers.d/user index 83fa47aa8..9ee44515d 100644 --- a/data/helpers.d/user +++ b/data/helpers.d/user @@ -89,16 +89,15 @@ ynh_system_group_exists() { # Create a system user # # examples: -# - ynh_system_user_create --username=nextcloud -> creates a nextcloud user with -# no home directory and /usr/sbin/nologin login shell (hence no login capability) -# - ynh_system_user_create --username=discourse --home_dir=/var/www/discourse --use_shell --> creates a -# discourse user using /var/www/discourse as home directory and the default login shell +# # Create a nextcloud user with no home directory and /usr/sbin/nologin login shell (hence no login capability) +# ynh_system_user_create --username=nextcloud +# # Create a discourse user using /var/www/discourse as home directory and the default login shell +# ynh_system_user_create --username=discourse --home_dir=/var/www/discourse --use_shell # # usage: ynh_system_user_create --username=user_name [--home_dir=home_dir] [--use_shell] # | arg: -u, --username - Name of the system user that will be create # | arg: -h, --home_dir - Path of the home dir for the user. Usually the final path of the app. If this argument is omitted, the user will be created without home -# | arg: -s, --use_shell - Create a user using the default login shell if present. -# If this argument is omitted, the user will be created with /usr/sbin/nologin shell +# | arg: -s, --use_shell - Create a user using the default login shell if present. If this argument is omitted, the user will be created with /usr/sbin/nologin shell # # Requires YunoHost version 2.6.4 or higher. ynh_system_user_create () { diff --git a/doc/generate_helper_doc.py b/doc/generate_helper_doc.py index 7d8c489b7..5b51dda02 100644 --- a/doc/generate_helper_doc.py +++ b/doc/generate_helper_doc.py @@ -4,7 +4,12 @@ import os import glob import datetime -def render(data): +def render(helpers): + + data = { "helpers": helpers, + "date": datetime.datetime.now().strftime("%m/%d/%Y"), + "version": open("../debian/changelog").readlines()[0].split()[1].strip("()") + } from jinja2 import Template from ansi2html import Ansi2HTMLConverter @@ -43,7 +48,7 @@ class Parser(): "code": [] } for i, line in enumerate(self.file): - + if line.startswith("#!/bin/bash"): continue @@ -103,7 +108,6 @@ class Parser(): b["usage"] = "" b["args"] = [] b["ret"] = "" - b["example"] = "" subblocks = '\n'.join(b["comments"]).split("\n\n") @@ -114,17 +118,29 @@ class Parser(): b["brief"] = subblock continue - elif subblock.startswith("example"): + elif subblock.startswith("example:"): b["example"] = " ".join(subblock.split()[1:]) continue + elif subblock.startswith("examples:"): + b["examples"] = subblock.split("\n")[1:] + continue + elif subblock.startswith("usage"): for line in subblock.split("\n"): if line.startswith("| arg"): - argname = line.split()[2] - argdescr = " ".join(line.split()[4:]) - b["args"].append((argname, argdescr)) + linesplit = line.split() + argname = linesplit[2] + # Detect that there's a long argument version (-f, --foo - Some description) + if argname.endswith(",") and linesplit[3].startswith("--"): + argname = argname.strip(",") + arglongname = linesplit[3] + argdescr = " ".join(linesplit[5:]) + b["args"].append((argname, arglongname, argdescr)) + else: + argdescr = " ".join(linesplit[4:]) + b["args"].append((argname, argdescr)) elif line.startswith("| ret"): b["ret"] = " ".join(line.split()[2:]) else: @@ -136,9 +152,17 @@ class Parser(): elif subblock.startswith("| arg"): for line in subblock.split("\n"): if line.startswith("| arg"): - argname = line.split()[2] - argdescr = line.split()[4:] - b["args"].append((argname, argdescr)) + linesplit = line.split() + argname = linesplit[2] + # Detect that there's a long argument version (-f, --foo - Some description) + if argname.endswith(",") and linesplit[3].startswith("--"): + argname = argname.strip(",") + arglongname = linesplit[3] + argdescr = " ".join(linesplit[5:]) + b["args"].append((argname, arglongname, argdescr)) + else: + argdescr = " ".join(linesplit[4:]) + b["args"].append((argname, argdescr)) continue else: diff --git a/doc/helper_doc_template.html b/doc/helper_doc_template.html index 1fa1f68ad..92611c737 100644 --- a/doc/helper_doc_template.html +++ b/doc/helper_doc_template.html @@ -2,7 +2,7 @@

App helpers

-{% for category, helpers in data %} +{% for category, helpers in data.helpers %}

{{ category }}

@@ -27,8 +27,12 @@

Arguments:

    - {% for name, descr in h.args %} -
  • {{ name }} : {{ descr }}
  • + {% for infos in h.args %} + {% if infos|length == 2 %} +
  • {{ infos[0] }} : {{ infos[1] }}
  • + {% else %} +
  • {{ infos[0] }}, {{ infos[1] }} : {{ infos[2] }}
  • + {% endif %} {% endfor %}

@@ -38,11 +42,25 @@ Returns: {{ h.ret }}

{% endif %} - {% if h.example %} + {% if "example" in h.keys() %}

Example: {{ h.example }}

{% endif %} + {% if "examples" in h.keys() %} +

+ Examples:

    + {% for example in h.examples %} + {% if not example.strip().startswith("# ") %} + {{ example }} + {% else %} + {{ example.strip("# ") }} + {% endif %} +
    + {% endfor %} +
+

+ {% endif %} {% if h.details %}

Details: @@ -63,6 +81,8 @@ {% endfor %} {% endfor %} +

Generated by this script on {{data.date}} (Yunohost version {{data.version}})

+