From ebb492fd1e61b8ed4a3fa39ab51b905af30ff399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 30 Dec 2019 14:43:10 +0100 Subject: [PATCH 001/482] Add force option in upgrade script --- data/actionsmap/yunohost.yml | 4 ++++ src/yunohost/app.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 3a4c9db97..af9b6b2e3 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -640,6 +640,10 @@ app: -f: full: --file help: Folder or tarball for upgrade + -F: + full: --force + help: Force the update, even though the app is up to date + action: store_true ### app_change_url() change-url: diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 30d3ab31b..76de6d86b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -410,7 +410,7 @@ def app_change_url(operation_logger, app, domain, path): hook_callback('post_app_change_url', args=args_list, env=env_dict) -def app_upgrade(app=[], url=None, file=None): +def app_upgrade(app=[], url=None, file=None, force=False): """ Upgrade app From ead80c72f8d136d8f178137b19c9dbfbb57de39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 30 Dec 2019 14:43:48 +0100 Subject: [PATCH 002/482] Implement upgrade type management and avoid unusefull upgrade --- src/yunohost/app.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 76de6d86b..97a3857a8 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -459,12 +459,39 @@ def app_upgrade(app=[], url=None, file=None, force=False): elif app_dict["upgradable"] == "url_required": logger.warning(m18n.n('custom_app_url_required', app=app_instance_name)) continue - elif app_dict["upgradable"] == "yes": + elif app_dict["upgradable"] == "yes" or force: manifest, extracted_app_folder = _fetch_app_from_git(app_instance_name) else: logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) continue + # Manage upgrade type and avoid any upgrade if there are nothing to do + upgrade_type = "UNKNOWN" + if manifest.get("upgrade_only_if_version_changes", None) is True: + # Get actual_version and new version + app_actual_version = manifest["version"] + app_new_version = app_dict["version"] + + # do only the upgrade if there are a change + if app_actual_version == app_new_version and not force: + logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) + # Save update time + now = int(time.time()) + app_setting(app_instance_name, 'update_time', now) + app_setting(app_instance_name, 'current_revision', manifest.get('remote', {}).get('revision', "?")) + continue + elif app_actual_version == app_new_version: + upgrade_type = "UPGRADE_FORCED" + elif "~ynh" in app_actual_version and "~ynh" in app_new_version: + app_actual_version_upstream, app_actual_version_pkg = app_actual_version.split("~ynh") + app_new_version_upstream, app_new_version_pkg = app_new_version.split("~ynh") + if app_actual_version_upstream == app_new_version_upstream: + upgrade_type = "UPGRADE_PACKAGE" + elif app_actual_version_pkg == app_new_version_pkg: + upgrade_type = "UPGRADE_APP" + else: + upgrade_type = "UPGRADE_FULL" + # Check requirements _check_manifest_requirements(manifest, app_instance_name=app_instance_name) _assert_system_is_sane_for_app(manifest, "pre") @@ -483,6 +510,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): env_dict["YNH_APP_ID"] = app_id env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) + env_dict["YNH_APP_UPGRADE_TYPE"] = upgrade_type # Start register change on system related_to = [('app', app_instance_name)] From 58cce48195e8711a3dd9ae7a22772dd5793b8307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 11 Apr 2020 22:52:42 +0200 Subject: [PATCH 003/482] Export old and new version in environnement --- data/helpers.d/utils | 6 ++---- src/yunohost/app.py | 13 ++++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index d449f0c39..9ea9294bc 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -405,8 +405,7 @@ ynh_app_upstream_version () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - manifest="${manifest:-../manifest.json}" - version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") + version_key=$YNH_APP_MANIFEST_VERSION echo "${version_key/~ynh*/}" } @@ -429,8 +428,7 @@ ynh_app_package_version () { # Manage arguments with getopts ynh_handle_getopts_args "$@" - manifest="${manifest:-../manifest.json}" - version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version") + version_key=$YNH_APP_MANIFEST_VERSION echo "${version_key/*~ynh/}" } diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 97a3857a8..d380235bf 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -347,6 +347,7 @@ def app_change_url(operation_logger, app, domain, path): env_dict["YNH_APP_ID"] = app_id env_dict["YNH_APP_INSTANCE_NAME"] = app env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) + env_dict["YNH_APP_MANIFEST_VERSION"] = manifest.get("version", "?") env_dict["YNH_APP_OLD_DOMAIN"] = old_domain env_dict["YNH_APP_OLD_PATH"] = old_path @@ -467,10 +468,11 @@ def app_upgrade(app=[], url=None, file=None, force=False): # Manage upgrade type and avoid any upgrade if there are nothing to do upgrade_type = "UNKNOWN" + # Get actual_version and new version + app_new_version = manifest.get("version", "?") + app_actual_version = app_dict.get("version", "?") + if manifest.get("upgrade_only_if_version_changes", None) is True: - # Get actual_version and new version - app_actual_version = manifest["version"] - app_new_version = app_dict["version"] # do only the upgrade if there are a change if app_actual_version == app_new_version and not force: @@ -511,6 +513,8 @@ def app_upgrade(app=[], url=None, file=None, force=False): env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) env_dict["YNH_APP_UPGRADE_TYPE"] = upgrade_type + env_dict["YNH_APP_MANIFEST_VERSION"] = app_new_version + env_dict["YNH_APP_OLD_VERSION"] = app_actual_version # Start register change on system related_to = [('app', app_instance_name)] @@ -723,6 +727,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu env_dict["YNH_APP_ID"] = app_id env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name env_dict["YNH_APP_INSTANCE_NUMBER"] = str(instance_number) + env_dict["YNH_APP_MANIFEST_VERSION"] = manifest.get("version", "?") # Start register change on system operation_logger.extra.update({'env': env_dict}) @@ -831,6 +836,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu env_dict_remove["YNH_APP_ID"] = app_id env_dict_remove["YNH_APP_INSTANCE_NAME"] = app_instance_name env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(instance_number) + env_dict["YNH_APP_MANIFEST_VERSION"] = manifest.get("version", "?") # Execute remove script operation_logger_remove = OperationLogger('remove_on_failed_install', @@ -1008,6 +1014,7 @@ def app_remove(operation_logger, app): env_dict["YNH_APP_ID"] = app_id env_dict["YNH_APP_INSTANCE_NAME"] = app env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) + env_dict["YNH_APP_MANIFEST_VERSION"] = manifest.get("version", "?") operation_logger.extra.update({'env': env_dict}) operation_logger.flush() From 640a46728078898251808210aec7e2b8c1c17d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Mon, 13 Apr 2020 16:59:32 +0200 Subject: [PATCH 004/482] Add helper ynh_compare_package_version --- data/helpers.d/utils | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 9ea9294bc..2cf9d2a7f 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -479,3 +479,52 @@ ynh_check_app_version_changed () { fi echo $return_value } + +# Compare the old package version and a other version passer as argument. +# This is really useful we we need to do some action only for some old package version. +# +# example: ynh_compare_package_version --comparaison gt --version 2.3.2~ynh1 +# In word this example will check if the installed version is grater than (gt) the version 2.3.2~ynh1 +# +# usage: ynh_compare_package_version --comparaision lt|gt|le|ge +# | arg: --comparaison - comparaison type. Could be : le (lower than), gt (grater than), le (lower or equals), ge (grater or equals) +# | arg: --version - The version to compare. Need to be a version in the yunohost package version type (like 2.3.1~ynh4) +# +# Requires YunoHost version 3.8.0 or higher. +ynh_compare_package_version() { + local legacy_args=cv + declare -Ar args_array=( [c]=comparaison= [v]=version= ) + + local version + local comparaison + local old_version=$YNH_APP_OLD_VERSION + # Manage arguments with getopts + ynh_handle_getopts_args "$@" + + if [[ ! $version =~ '~ynh' ]] || [[ ! $old_version =~ '~ynh' ]]; then + ynh_print_warn "Invalid agument for version." + return 1 + fi + + if [ $version == $old_version ]; then + if [ $comparaison == ge ] || [ $comparaison == le ]; then + return 0 + else + return 1 + fi + fi + + if [ $comparaison == ge ] || [ $comparaison == gt ]; then + if [ $(printf "$version\n$old_version" | sort -V | tail -n 1) == $old_version ]; then + return 0 + else + return 1 + fi + else + if [ $(printf "$version\n$old_version" | sort -V | tail -n 1) == $version ]; then + return 0 + else + return 1 + fi + fi +} From 3484f73506e75cb4d44beb2bdb40f6b5509b8cb3 Mon Sep 17 00:00:00 2001 From: Maniack Crudelis Date: Tue, 14 Apr 2020 12:35:35 +0200 Subject: [PATCH 005/482] Clean and syntax --- data/helpers.d/utils | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 576fa5a56..c491b1aa0 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -486,48 +486,58 @@ ynh_check_app_version_changed () { echo $return_value } -# Compare the old package version and a other version passer as argument. -# This is really useful we we need to do some action only for some old package version. +# Compare the current package version against another version given as an argument. +# This is really useful when we need to do some actions only for some old package versions. # -# example: ynh_compare_package_version --comparaison gt --version 2.3.2~ynh1 -# In word this example will check if the installed version is grater than (gt) the version 2.3.2~ynh1 +# example: ynh_compare_package_version --comparison gt --version 2.3.2~ynh1 +# This example will check if the installed version is greater than (gt) the version 2.3.2~ynh1 # -# usage: ynh_compare_package_version --comparaision lt|gt|le|ge -# | arg: --comparaison - comparaison type. Could be : le (lower than), gt (grater than), le (lower or equals), ge (grater or equals) +# usage: ynh_compare_package_version --comparison lt|gt|le|ge +# | arg: --comparison - Comparison type. Could be : le (lower than), gt (greater than), le (lower or equal), ge (greater or equal) # | arg: --version - The version to compare. Need to be a version in the yunohost package version type (like 2.3.1~ynh4) # +# Return 0 if the evaluation is true. 1 if false. +# # Requires YunoHost version 3.8.0 or higher. ynh_compare_package_version() { local legacy_args=cv - declare -Ar args_array=( [c]=comparaison= [v]=version= ) - + declare -Ar args_array=( [c]=comparison= [v]=version= ) local version - local comparaison - local old_version=$YNH_APP_OLD_VERSION + local comparison # Manage arguments with getopts ynh_handle_getopts_args "$@" - if [[ ! $version =~ '~ynh' ]] || [[ ! $old_version =~ '~ynh' ]]; then + local current_version=$YNH_APP_OLD_VERSION + + # Check the syntax of the versions + if [[ ! $version =~ '~ynh' ]] || [[ ! $current_version =~ '~ynh' ]] + then ynh_print_warn "Invalid agument for version." return 1 fi - if [ $version == $old_version ]; then - if [ $comparaison == ge ] || [ $comparaison == le ]; then + # If the version are identical, and the evaluation allows equal versions. + if [ $version == $current_version ] + then + if [ $comparison == ge ] || [ $comparison == le ]; then return 0 else return 1 fi fi - if [ $comparaison == ge ] || [ $comparaison == gt ]; then - if [ $(printf "$version\n$old_version" | sort -V | tail -n 1) == $old_version ]; then + # Check if the current version is greater than the one given as argument + if [ $comparison == ge ] || [ $comparison == gt ] + then + if [ $(printf "$version\n$current_version" | sort --version-sort | tail -n 1) == $current_version ]; then return 0 else return 1 fi + + # Else if the current version is lower than the one given as argument else - if [ $(printf "$version\n$old_version" | sort -V | tail -n 1) == $version ]; then + if [ $(printf "$version\n$current_version" | sort --version-sort | tail -n 1) == $version ]; then return 0 else return 1 From 14ef523585834aff3cfd280020ce5f06f4aeda20 Mon Sep 17 00:00:00 2001 From: Josue-T Date: Tue, 14 Apr 2020 13:45:05 +0200 Subject: [PATCH 006/482] Improve env_dict variable in upgrade Co-Authored-By: Maniack Crudelis --- 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 ff112ec14..5e4e9abf4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -514,7 +514,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) env_dict["YNH_APP_UPGRADE_TYPE"] = upgrade_type env_dict["YNH_APP_MANIFEST_VERSION"] = app_new_version - env_dict["YNH_APP_OLD_VERSION"] = app_actual_version + env_dict["YNH_APP_CURRENT_VERSION"] = app_actual_version # Start register change on system related_to = [('app', app_instance_name)] From 3d51e235e89815ce6da247ccaa92cb5ff956e54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 14 Apr 2020 13:56:19 +0200 Subject: [PATCH 007/482] Add comments --- data/helpers.d/utils | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index c491b1aa0..759d9f1b1 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -492,6 +492,12 @@ ynh_check_app_version_changed () { # example: ynh_compare_package_version --comparison gt --version 2.3.2~ynh1 # This example will check if the installed version is greater than (gt) the version 2.3.2~ynh1 # +# Generally you might probably use it as follow in the upgrade script +# +# if ynh_compare_package_version --comparaison gt --version 2.3.2~ynh1; then +# # Do something that is needed for the package version older than 2.3.2~ynh1 +# fi +# # usage: ynh_compare_package_version --comparison lt|gt|le|ge # | arg: --comparison - Comparison type. Could be : le (lower than), gt (greater than), le (lower or equal), ge (greater or equal) # | arg: --version - The version to compare. Need to be a version in the yunohost package version type (like 2.3.1~ynh4) @@ -519,6 +525,7 @@ ynh_compare_package_version() { # If the version are identical, and the evaluation allows equal versions. if [ $version == $current_version ] then + # manage equals case. if [ $comparison == ge ] || [ $comparison == le ]; then return 0 else From fec5d3d084849980e43d2b5bc49da6d939797879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 14 Apr 2020 14:01:01 +0200 Subject: [PATCH 008/482] Rename variable --- data/helpers.d/utils | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 759d9f1b1..65b6d0d1c 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -513,7 +513,7 @@ ynh_compare_package_version() { # Manage arguments with getopts ynh_handle_getopts_args "$@" - local current_version=$YNH_APP_OLD_VERSION + local current_version=$YNH_APP_CURRENT_VERSION # Check the syntax of the versions if [[ ! $version =~ '~ynh' ]] || [[ ! $current_version =~ '~ynh' ]] From 17e8bdedf6ade8cfed69cf4ec7895c688c925e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 14 Apr 2020 14:03:04 +0200 Subject: [PATCH 009/482] Rename heper --- data/helpers.d/utils | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 65b6d0d1c..e1fdc1333 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -489,23 +489,24 @@ ynh_check_app_version_changed () { # Compare the current package version against another version given as an argument. # This is really useful when we need to do some actions only for some old package versions. # -# example: ynh_compare_package_version --comparison gt --version 2.3.2~ynh1 +# example: ynh_compare_current_package_version --comparison gt --version 2.3.2~ynh1 # This example will check if the installed version is greater than (gt) the version 2.3.2~ynh1 # # Generally you might probably use it as follow in the upgrade script # -# if ynh_compare_package_version --comparaison gt --version 2.3.2~ynh1; then +# if ynh_compare_current_package_version --comparaison gt --version 2.3.2~ynh1 +# then # # Do something that is needed for the package version older than 2.3.2~ynh1 # fi # -# usage: ynh_compare_package_version --comparison lt|gt|le|ge +# usage: ynh_compare_current_package_version --comparison lt|gt|le|ge # | arg: --comparison - Comparison type. Could be : le (lower than), gt (greater than), le (lower or equal), ge (greater or equal) # | arg: --version - The version to compare. Need to be a version in the yunohost package version type (like 2.3.1~ynh4) # # Return 0 if the evaluation is true. 1 if false. # # Requires YunoHost version 3.8.0 or higher. -ynh_compare_package_version() { +ynh_compare_current_package_version() { local legacy_args=cv declare -Ar args_array=( [c]=comparison= [v]=version= ) local version From 5315807ea76a8c2a0c23e49e2b7681202fb7f23f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Tue, 14 Apr 2020 14:04:26 +0200 Subject: [PATCH 010/482] Cleanup comment --- data/helpers.d/utils | 1 - 1 file changed, 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index e1fdc1333..f9db320d5 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -526,7 +526,6 @@ ynh_compare_current_package_version() { # If the version are identical, and the evaluation allows equal versions. if [ $version == $current_version ] then - # manage equals case. if [ $comparison == ge ] || [ $comparison == le ]; then return 0 else From 35785888608c370f8dc00c0b664f290df0e9bf6d Mon Sep 17 00:00:00 2001 From: Josue-T Date: Wed, 15 Apr 2020 11:53:39 +0200 Subject: [PATCH 011/482] Replace actual by current Co-Authored-By: Alexandre Aubin --- src/yunohost/app.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 5e4e9abf4..baccb3359 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -468,28 +468,28 @@ def app_upgrade(app=[], url=None, file=None, force=False): # Manage upgrade type and avoid any upgrade if there are nothing to do upgrade_type = "UNKNOWN" - # Get actual_version and new version + # Get current_version and new version app_new_version = manifest.get("version", "?") - app_actual_version = app_dict.get("version", "?") + app_current_version = app_dict.get("version", "?") if manifest.get("upgrade_only_if_version_changes", None) is True: # do only the upgrade if there are a change - if app_actual_version == app_new_version and not force: + if app_current_version == app_new_version and not force: logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) # Save update time now = int(time.time()) app_setting(app_instance_name, 'update_time', now) app_setting(app_instance_name, 'current_revision', manifest.get('remote', {}).get('revision', "?")) continue - elif app_actual_version == app_new_version: + elif app_current_version == app_new_version: upgrade_type = "UPGRADE_FORCED" - elif "~ynh" in app_actual_version and "~ynh" in app_new_version: - app_actual_version_upstream, app_actual_version_pkg = app_actual_version.split("~ynh") + elif "~ynh" in app_current_version and "~ynh" in app_new_version: + app_current_version_upstream, app_current_version_pkg = app_current_version.split("~ynh") app_new_version_upstream, app_new_version_pkg = app_new_version.split("~ynh") - if app_actual_version_upstream == app_new_version_upstream: + if app_current_version_upstream == app_new_version_upstream: upgrade_type = "UPGRADE_PACKAGE" - elif app_actual_version_pkg == app_new_version_pkg: + elif app_current_version_pkg == app_new_version_pkg: upgrade_type = "UPGRADE_APP" else: upgrade_type = "UPGRADE_FULL" From f416b94fb8915aa38b8dabfaf19d553783be4e8f Mon Sep 17 00:00:00 2001 From: Josue-T Date: Wed, 15 Apr 2020 11:54:55 +0200 Subject: [PATCH 012/482] Put upgrade_only_if_version_changes in integration section Co-Authored-By: Alexandre Aubin --- 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 baccb3359..938984167 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -472,7 +472,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): app_new_version = manifest.get("version", "?") app_current_version = app_dict.get("version", "?") - if manifest.get("upgrade_only_if_version_changes", None) is True: + if manifest.get('integration', {}).get("upgrade_only_if_version_changes", None) is True: # do only the upgrade if there are a change if app_current_version == app_new_version and not force: From d947724b70df83f7ec2a6448490ef660286e6748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 15 Apr 2020 11:57:21 +0200 Subject: [PATCH 013/482] Fix typo --- data/helpers.d/utils | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index f9db320d5..68ce6c7f2 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -494,7 +494,7 @@ ynh_check_app_version_changed () { # # Generally you might probably use it as follow in the upgrade script # -# if ynh_compare_current_package_version --comparaison gt --version 2.3.2~ynh1 +# if ynh_compare_current_package_version --comparaison lt --version 2.3.2~ynh1 # then # # Do something that is needed for the package version older than 2.3.2~ynh1 # fi From ceeb34f68edc67d277ac8187ee14d5493af72f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 15 Apr 2020 12:07:45 +0200 Subject: [PATCH 014/482] Use 'dpkg --compare-versions' --- data/helpers.d/utils | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 68ce6c7f2..29eba2f07 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -499,8 +499,9 @@ ynh_check_app_version_changed () { # # Do something that is needed for the package version older than 2.3.2~ynh1 # fi # -# usage: ynh_compare_current_package_version --comparison lt|gt|le|ge -# | arg: --comparison - Comparison type. Could be : le (lower than), gt (greater than), le (lower or equal), ge (greater or equal) +# usage: ynh_compare_current_package_version --comparison lt|le|eq|ne|ge|gt +# | arg: --comparison - Comparison type. Could be : le (lower than), le (lower or equal), +# | eq (equal), ne (not equal), ge (greater or equal), gt (greater than) # | arg: --version - The version to compare. Need to be a version in the yunohost package version type (like 2.3.1~ynh4) # # Return 0 if the evaluation is true. 1 if false. @@ -519,35 +520,14 @@ ynh_compare_current_package_version() { # Check the syntax of the versions if [[ ! $version =~ '~ynh' ]] || [[ ! $current_version =~ '~ynh' ]] then - ynh_print_warn "Invalid agument for version." - return 1 + ynh_die "Invalid argument for version." fi - # If the version are identical, and the evaluation allows equal versions. - if [ $version == $current_version ] - then - if [ $comparison == ge ] || [ $comparison == le ]; then - return 0 - else - return 1 - fi + # Check validity of the comparator + if [[ ! $comparison =~ (lt|le|eq|ne|ge|gt) ]]; then + ynh_die "Invialid comparator must be : lt, le, eq, ne, ge, gt" fi - # Check if the current version is greater than the one given as argument - if [ $comparison == ge ] || [ $comparison == gt ] - then - if [ $(printf "$version\n$current_version" | sort --version-sort | tail -n 1) == $current_version ]; then - return 0 - else - return 1 - fi - - # Else if the current version is lower than the one given as argument - else - if [ $(printf "$version\n$current_version" | sort --version-sort | tail -n 1) == $version ]; then - return 0 - else - return 1 - fi - fi + # Return the return value of dpkg --compare-versions + dpkg --compare-versions $current_version $comparison $version } From 8dd3986cace2d771e3ad3e400331b89e6330c96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 15 Apr 2020 12:17:01 +0200 Subject: [PATCH 015/482] Fix rename variable --- 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 938984167..57e89e2a7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -514,7 +514,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) env_dict["YNH_APP_UPGRADE_TYPE"] = upgrade_type env_dict["YNH_APP_MANIFEST_VERSION"] = app_new_version - env_dict["YNH_APP_CURRENT_VERSION"] = app_actual_version + env_dict["YNH_APP_CURRENT_VERSION"] = app_current_version # Start register change on system related_to = [('app', app_instance_name)] From 4f0d5cef964faf62164a1f7d8bca9426ded07314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 15 Apr 2020 16:25:35 +0200 Subject: [PATCH 016/482] Improve version management in '_app_upgradable' --- src/yunohost/app.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 57e89e2a7..bdfead85c 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -156,10 +156,18 @@ def app_info(app, full=False): def _app_upgradable(app_infos): + from packaging import version # Determine upgradability # In case there is neither update_time nor install_time, we assume the app can/has to be upgraded + # Firstly use the version to know if an upgrade is available + if app_infos["version"] != "-" and app_infos["from_catalog"]["manifest"].get("version", None): + if version.parse(app_infos["version"]) < version.parse(app_infos["from_catalog"]["manifest"].get("version", "-")): + return "yes" + else: + return "no" + if not app_infos.get("from_catalog", None): return "url_required" if not app_infos["from_catalog"].get("lastUpdate") or not app_infos["from_catalog"].get("git"): From a096a36e27c1606bc5cc27664c97a96335229c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Wed, 15 Apr 2020 16:30:11 +0200 Subject: [PATCH 017/482] Also manage downgrade --- src/yunohost/app.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index bdfead85c..9305673d7 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -429,6 +429,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): url -- Git url to fetch for upgrade """ + from packaging import version from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.permission import permission_sync_to_user @@ -483,13 +484,15 @@ def app_upgrade(app=[], url=None, file=None, force=False): if manifest.get('integration', {}).get("upgrade_only_if_version_changes", None) is True: # do only the upgrade if there are a change - if app_current_version == app_new_version and not force: + if version.parse(app_current_version) >= version.parse(app_new_version) and not force: logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) # Save update time now = int(time.time()) app_setting(app_instance_name, 'update_time', now) app_setting(app_instance_name, 'current_revision', manifest.get('remote', {}).get('revision', "?")) continue + elif version.parse(app_current_version) > version.parse(app_new_version): + upgrade_type = "DOWNGRADE_FORCED" elif app_current_version == app_new_version: upgrade_type = "UPGRADE_FORCED" elif "~ynh" in app_current_version and "~ynh" in app_new_version: From 9389f4669cd061d6026ef6102ce8190d853488d1 Mon Sep 17 00:00:00 2001 From: yalh76 Date: Wed, 15 Apr 2020 21:13:46 +0200 Subject: [PATCH 018/482] simplification --- data/helpers.d/utils | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 29eba2f07..b6f3e7071 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -489,8 +489,8 @@ ynh_check_app_version_changed () { # Compare the current package version against another version given as an argument. # This is really useful when we need to do some actions only for some old package versions. # -# example: ynh_compare_current_package_version --comparison gt --version 2.3.2~ynh1 -# This example will check if the installed version is greater than (gt) the version 2.3.2~ynh1 +# example: ynh_compare_current_package_version --comparison lt --version 2.3.2~ynh1 +# This example will check if the installed version is lower than (lt) the version 2.3.2~ynh1 # # Generally you might probably use it as follow in the upgrade script # @@ -500,7 +500,7 @@ ynh_check_app_version_changed () { # fi # # usage: ynh_compare_current_package_version --comparison lt|le|eq|ne|ge|gt -# | arg: --comparison - Comparison type. Could be : le (lower than), le (lower or equal), +# | arg: --comparison - Comparison type. Could be : lt (lower than), le (lower or equal), # | eq (equal), ne (not equal), ge (greater or equal), gt (greater than) # | arg: --version - The version to compare. Need to be a version in the yunohost package version type (like 2.3.1~ynh4) # From e7970d8571495e0814914389ae3e6e6f0863b91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 23 Apr 2020 14:30:37 +0200 Subject: [PATCH 019/482] Check settings 'upgrade_only_if_version_changes' before to check update availability --- src/yunohost/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 9305673d7..055ee8f29 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -162,7 +162,8 @@ def _app_upgradable(app_infos): # In case there is neither update_time nor install_time, we assume the app can/has to be upgraded # Firstly use the version to know if an upgrade is available - if app_infos["version"] != "-" and app_infos["from_catalog"]["manifest"].get("version", None): + if app_infos["manifest"].get('integration', {}).get("upgrade_only_if_version_changes", None) is True and \ + '~ynh' in app_infos["version"] and app_infos["from_catalog"]["manifest"].get("version", None): if version.parse(app_infos["version"]) < version.parse(app_infos["from_catalog"]["manifest"].get("version", "-")): return "yes" else: From 1826e3c5b66044940317e4296429e0b11b08035a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Thu, 23 Apr 2020 14:31:05 +0200 Subject: [PATCH 020/482] Make more robust version management in upgrade --- src/yunohost/app.py | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 055ee8f29..74a626597 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -483,28 +483,30 @@ def app_upgrade(app=[], url=None, file=None, force=False): app_current_version = app_dict.get("version", "?") if manifest.get('integration', {}).get("upgrade_only_if_version_changes", None) is True: - - # do only the upgrade if there are a change - if version.parse(app_current_version) >= version.parse(app_new_version) and not force: - logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) - # Save update time - now = int(time.time()) - app_setting(app_instance_name, 'update_time', now) - app_setting(app_instance_name, 'current_revision', manifest.get('remote', {}).get('revision', "?")) - continue - elif version.parse(app_current_version) > version.parse(app_new_version): - upgrade_type = "DOWNGRADE_FORCED" - elif app_current_version == app_new_version: - upgrade_type = "UPGRADE_FORCED" - elif "~ynh" in app_current_version and "~ynh" in app_new_version: - app_current_version_upstream, app_current_version_pkg = app_current_version.split("~ynh") - app_new_version_upstream, app_new_version_pkg = app_new_version.split("~ynh") - if app_current_version_upstream == app_new_version_upstream: - upgrade_type = "UPGRADE_PACKAGE" - elif app_current_version_pkg == app_new_version_pkg: - upgrade_type = "UPGRADE_APP" + if "~ynh" in app_current_version and "~ynh" in app_new_version: + if version.parse(app_current_version) >= version.parse(app_new_version) and not force: + # No new version available + logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) + # Save update time + now = int(time.time()) + app_setting(app_instance_name, 'update_time', now) + app_setting(app_instance_name, 'current_revision', manifest.get('remote', {}).get('revision', "?")) + continue + elif version.parse(app_current_version) > version.parse(app_new_version): + upgrade_type = "DOWNGRADE_FORCED" + elif app_current_version == app_new_version: + upgrade_type = "UPGRADE_FORCED" else: - upgrade_type = "UPGRADE_FULL" + app_current_version_upstream, app_current_version_pkg = app_current_version.split("~ynh") + app_new_version_upstream, app_new_version_pkg = app_new_version.split("~ynh") + if app_current_version_upstream == app_new_version_upstream: + upgrade_type = "UPGRADE_PACKAGE" + elif app_current_version_pkg == app_new_version_pkg: + upgrade_type = "UPGRADE_APP" + else: + upgrade_type = "UPGRADE_FULL" + else: + logger.warning("/!\\ Packagers ! You have enabled the setting 'upgrade_only_if_version_changes' but you haven't used the official way to define the package version") # Check requirements _check_manifest_requirements(manifest, app_instance_name=app_instance_name) From c34de0b792177676939c97a9d66f12e95bec3d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Fri, 24 Apr 2020 14:26:31 +0200 Subject: [PATCH 021/482] Improve version management in catalog --- src/yunohost/app.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 74a626597..5f0084f08 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -162,9 +162,13 @@ def _app_upgradable(app_infos): # In case there is neither update_time nor install_time, we assume the app can/has to be upgraded # Firstly use the version to know if an upgrade is available - if app_infos["manifest"].get('integration', {}).get("upgrade_only_if_version_changes", None) is True and \ - '~ynh' in app_infos["version"] and app_infos["from_catalog"]["manifest"].get("version", None): - if version.parse(app_infos["version"]) < version.parse(app_infos["from_catalog"]["manifest"].get("version", "-")): + app_is_in_catalog = bool(app_infos.get("from_catalog")) + upgrade_only_if_version_changes = app_infos["manifest"].get('integration', {}).get("upgrade_only_if_version_changes", None) is True + installed_version = version.parse(app_infos["version"]) + version_in_catalog = version.parse(app_infos.get("from_catalog", {}).get("manifest", {}).get("version", "0~ynh0")) + + if app_is_in_catalog and '~ynh' in app_infos["version"]: + if upgrade_only_if_version_changes and installed_version < version_in_catalog: return "yes" else: return "no" From c0c026613f18de3741d091581ac700e83d026ef1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Apr 2020 02:15:14 +0200 Subject: [PATCH 022/482] Add wss: to default to get rid of angry CSP on webadmin --- data/templates/nginx/security.conf.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc index ff3d2ee99..0a8bd90b6 100644 --- a/data/templates/nginx/security.conf.inc +++ b/data/templates/nginx/security.conf.inc @@ -22,7 +22,7 @@ ssl_prefer_server_ciphers off; # https://wiki.mozilla.org/Security/Guidelines/Web_Security # https://observatory.mozilla.org/ 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 "Content-Security-Policy-Report-Only : default-src https: data: wss: 'unsafe-inline' '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"; From ce6c33aa9000e838cf483ad42071461e315eea4f Mon Sep 17 00:00:00 2001 From: Josue-T Date: Mon, 27 Apr 2020 11:05:01 +0200 Subject: [PATCH 023/482] Fix version key in installed version Co-Authored-By: Alexandre Aubin --- 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 5f0084f08..d23877035 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -164,7 +164,7 @@ def _app_upgradable(app_infos): # Firstly use the version to know if an upgrade is available app_is_in_catalog = bool(app_infos.get("from_catalog")) upgrade_only_if_version_changes = app_infos["manifest"].get('integration', {}).get("upgrade_only_if_version_changes", None) is True - installed_version = version.parse(app_infos["version"]) + installed_version = version.parse(app_infos.get("version", "0~ynh0")) version_in_catalog = version.parse(app_infos.get("from_catalog", {}).get("manifest", {}).get("version", "0~ynh0")) if app_is_in_catalog and '~ynh' in app_infos["version"]: From f2791c911f71cb253b1f6e4ea119d8a4e17e6339 Mon Sep 17 00:00:00 2001 From: Josue-T Date: Mon, 27 Apr 2020 11:08:36 +0200 Subject: [PATCH 024/482] Make more rebobut version check Co-Authored-By: Alexandre Aubin --- 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 d23877035..2d087a0d4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -167,7 +167,7 @@ def _app_upgradable(app_infos): installed_version = version.parse(app_infos.get("version", "0~ynh0")) version_in_catalog = version.parse(app_infos.get("from_catalog", {}).get("manifest", {}).get("version", "0~ynh0")) - if app_is_in_catalog and '~ynh' in app_infos["version"]: + if app_is_in_catalog and '~ynh' in str(installed_version) and '~ynh' in str(version_in_catalog): if upgrade_only_if_version_changes and installed_version < version_in_catalog: return "yes" else: From 01d5c91e60c7cc08f12058ddd893b587d9d6ccb9 Mon Sep 17 00:00:00 2001 From: Josue-T Date: Mon, 27 Apr 2020 11:09:10 +0200 Subject: [PATCH 025/482] Simply code Co-Authored-By: Alexandre Aubin --- 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 2d087a0d4..3d17869f5 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -173,7 +173,7 @@ def _app_upgradable(app_infos): else: return "no" - if not app_infos.get("from_catalog", None): + if not app_is_in_catalog: return "url_required" if not app_infos["from_catalog"].get("lastUpdate") or not app_infos["from_catalog"].get("git"): return "url_required" From 72b412c6d3d9a18489b1d288425201a18078112a Mon Sep 17 00:00:00 2001 From: Josue-T Date: Mon, 27 Apr 2020 11:16:40 +0200 Subject: [PATCH 026/482] Cleanup code indentation --- src/yunohost/app.py | 58 +++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 3d17869f5..45cad35b8 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -480,37 +480,39 @@ def app_upgrade(app=[], url=None, file=None, force=False): logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) continue - # Manage upgrade type and avoid any upgrade if there are nothing to do +# Manage upgrade type and avoid any upgrade if there is nothing to do upgrade_type = "UNKNOWN" + upgrade_only_if_version_changes = manifest.get('integration', {}).get("upgrade_only_if_version_changes", None) is True # Get current_version and new version - app_new_version = manifest.get("version", "?") - app_current_version = app_dict.get("version", "?") - - if manifest.get('integration', {}).get("upgrade_only_if_version_changes", None) is True: - if "~ynh" in app_current_version and "~ynh" in app_new_version: - if version.parse(app_current_version) >= version.parse(app_new_version) and not force: - # No new version available - logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) - # Save update time - now = int(time.time()) - app_setting(app_instance_name, 'update_time', now) - app_setting(app_instance_name, 'current_revision', manifest.get('remote', {}).get('revision', "?")) - continue - elif version.parse(app_current_version) > version.parse(app_new_version): - upgrade_type = "DOWNGRADE_FORCED" - elif app_current_version == app_new_version: - upgrade_type = "UPGRADE_FORCED" - else: - app_current_version_upstream, app_current_version_pkg = app_current_version.split("~ynh") - app_new_version_upstream, app_new_version_pkg = app_new_version.split("~ynh") - if app_current_version_upstream == app_new_version_upstream: - upgrade_type = "UPGRADE_PACKAGE" - elif app_current_version_pkg == app_new_version_pkg: - upgrade_type = "UPGRADE_APP" - else: - upgrade_type = "UPGRADE_FULL" + app_new_version = version.parse(manifest.get("version", "?")) + app_current_version = version.parse(app_dict.get("version", "?")) + if "~ynh" not in str(app_current_version) or "~ynh" not in str(app_new_version): + logger.warning("/!\\ Packagers ! You have enabled the setting 'upgrade_only_if_version_changes' but you haven't used the official way to define the package version") + upgrade_only_if_version_changes = False + if upgrade_only_if_version_changes: + if app_current_version >= app_new_version and not force: + # In case of upgrade from file or custom repository + # No new version available + logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) + # Save update time + now = int(time.time()) + app_setting(app_instance_name, 'update_time', now) + app_setting(app_instance_name, 'current_revision', manifest.get('remote', {}).get('revision', "?")) + continue + elif app_current_version > app_new_version: + upgrade_type = "DOWNGRADE_FORCED" + elif app_current_version == app_new_version: + upgrade_type = "UPGRADE_FORCED" else: - logger.warning("/!\\ Packagers ! You have enabled the setting 'upgrade_only_if_version_changes' but you haven't used the official way to define the package version") + app_current_version_upstream, app_current_version_pkg = str(app_current_version).split("~ynh") + app_new_version_upstream, app_new_version_pkg = str(app_new_version).split("~ynh") + if app_current_version_upstream == app_new_version_upstream: + upgrade_type = "UPGRADE_PACKAGE" + elif app_current_version_pkg == app_new_version_pkg: + upgrade_type = "UPGRADE_APP" + else: + upgrade_type = "UPGRADE_FULL" + # Check requirements _check_manifest_requirements(manifest, app_instance_name=app_instance_name) From e01859ffc4b8aa6759adc75ff8920eff59d23026 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 27 Apr 2020 20:57:38 +0200 Subject: [PATCH 027/482] Fix tests --- src/yunohost/app.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 45cad35b8..2ab729a37 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -166,7 +166,7 @@ def _app_upgradable(app_infos): upgrade_only_if_version_changes = app_infos["manifest"].get('integration', {}).get("upgrade_only_if_version_changes", None) is True installed_version = version.parse(app_infos.get("version", "0~ynh0")) version_in_catalog = version.parse(app_infos.get("from_catalog", {}).get("manifest", {}).get("version", "0~ynh0")) - + if app_is_in_catalog and '~ynh' in str(installed_version) and '~ynh' in str(version_in_catalog): if upgrade_only_if_version_changes and installed_version < version_in_catalog: return "yes" @@ -480,7 +480,7 @@ def app_upgrade(app=[], url=None, file=None, force=False): logger.success(m18n.n('app_already_up_to_date', app=app_instance_name)) continue -# Manage upgrade type and avoid any upgrade if there is nothing to do + # Manage upgrade type and avoid any upgrade if there is nothing to do upgrade_type = "UNKNOWN" upgrade_only_if_version_changes = manifest.get('integration', {}).get("upgrade_only_if_version_changes", None) is True # Get current_version and new version @@ -533,8 +533,8 @@ def app_upgrade(app=[], url=None, file=None, force=False): env_dict["YNH_APP_INSTANCE_NAME"] = app_instance_name env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) env_dict["YNH_APP_UPGRADE_TYPE"] = upgrade_type - env_dict["YNH_APP_MANIFEST_VERSION"] = app_new_version - env_dict["YNH_APP_CURRENT_VERSION"] = app_current_version + env_dict["YNH_APP_MANIFEST_VERSION"] = str(app_new_version) + env_dict["YNH_APP_CURRENT_VERSION"] = str(app_current_version) # Start register change on system related_to = [('app', app_instance_name)] From 0fba21f92495668e84591522fd49e7813e38bab9 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 01:07:07 +0200 Subject: [PATCH 028/482] Enforce CSP rules for real on webadmin --- data/templates/nginx/plain/yunohost_admin.conf.inc | 3 +++ data/templates/nginx/security.conf.inc | 2 +- data/templates/nginx/yunohost_admin.conf | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/templates/nginx/plain/yunohost_admin.conf.inc b/data/templates/nginx/plain/yunohost_admin.conf.inc index 2ab72293d..8b81ab932 100644 --- a/data/templates/nginx/plain/yunohost_admin.conf.inc +++ b/data/templates/nginx/plain/yunohost_admin.conf.inc @@ -6,6 +6,9 @@ location /yunohost/admin/ { default_type text/html; index index.html; + more_set_headers "Content-Security-Policy: upgrade-insecure-requests; default-src 'self'; connect-src 'self' https://raw.githubusercontent.com https://paste.yunohost.org wss://$host; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; object-src 'none';"; + more_set_headers "Content-Security-Policy-Report-Only:"; + # Short cache on handlebars templates location ~* \.(?:ms)$ { expires 5m; diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc index 0a8bd90b6..dea0f49db 100644 --- a/data/templates/nginx/security.conf.inc +++ b/data/templates/nginx/security.conf.inc @@ -22,7 +22,7 @@ ssl_prefer_server_ciphers off; # https://wiki.mozilla.org/Security/Guidelines/Web_Security # https://observatory.mozilla.org/ more_set_headers "Content-Security-Policy : upgrade-insecure-requests"; -more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: wss: 'unsafe-inline' 'unsafe-eval' "; +more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' '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"; diff --git a/data/templates/nginx/yunohost_admin.conf b/data/templates/nginx/yunohost_admin.conf index 3df838c4a..d13dbfe90 100644 --- a/data/templates/nginx/yunohost_admin.conf +++ b/data/templates/nginx/yunohost_admin.conf @@ -22,7 +22,6 @@ server { 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'"; location / { return 302 https://$http_host/yunohost/admin; From 31e868e82d3cc328705e5278a05c94537708409a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 03:49:37 +0200 Subject: [PATCH 029/482] Enforce permissions for stuff in /etc/yunohost/ --- data/hooks/conf_regen/01-yunohost | 25 +++++++++++++++++++++++++ data/hooks/conf_regen/06-slapd | 3 --- data/hooks/conf_regen/12-metronome | 3 +++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index 236619079..b24689023 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -65,6 +65,30 @@ EOF } +do_post_regen() { + regen_conf_files=$1 + + ###################### + # Enfore permissions # + ###################### + + # Certs + # We do this with find because there could be a lot of them... + chown -R root:ssl-cert /etc/yunohost/certs + chmod 750 /etc/yunohost/certs + find /etc/yunohost/certs/ -type f -exec chmod 640 {} \; + find /etc/yunohost/certs/ -type d -exec chmod 750 {} \; + + # Misc configuration / state files + chown root:root $(ls /etc/yunohost/{*.yml,*.yaml,*.json,mysql,psql} 2>/dev/null) + chmod 600 $(ls /etc/yunohost/{*.yml,*.yaml,*.json,mysql,psql} 2>/dev/null) + + # Apps folder, custom hooks folder + [[ ! -e /etc/yunohost/hooks.d ]] || (chown root /etc/yunohost/hooks.d && chmod 700 /etc/yunohost/hooks.d) + [[ ! -e /etc/yunohost/apps ]] || (chown root /etc/yunohost/apps && chmod 700 /etc/yunohost/apps) + +} + _update_services() { python2 - << EOF import yaml @@ -132,6 +156,7 @@ case "$1" in do_pre_regen $4 ;; post) + do_post_regen $4 ;; init) do_init_regen diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 9b2c20138..5fd727a2d 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -82,9 +82,6 @@ do_post_regen() { chown root:openldap /etc/ldap/slapd.conf chown -R openldap:openldap /etc/ldap/schema/ chown -R openldap:openldap /etc/ldap/slapd.d/ - chown -R root:ssl-cert /etc/yunohost/certs/yunohost.org/ - chmod o-rwx /etc/yunohost/certs/yunohost.org/ - chmod -R g+rx /etc/yunohost/certs/yunohost.org/ # If we changed the systemd ynh-override conf if echo "$regen_conf_files" | sed 's/,/\n/g' | grep -q "^/etc/systemd/system/slapd.service.d/ynh-override.conf$" diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome index 55433e13c..897463eb0 100755 --- a/data/hooks/conf_regen/12-metronome +++ b/data/hooks/conf_regen/12-metronome @@ -55,6 +55,9 @@ do_post_regen() { done # fix some permissions + + # metronome should be in ssl-cert group to let it access SSL certificates + usermod -aG ssl-cert metronome chown -R metronome: /var/lib/metronome/ chown -R metronome: /etc/metronome/conf.d/ From e63679684a31dda638b98396e9dd59640d9622d9 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 12 Apr 2020 23:28:49 +0200 Subject: [PATCH 030/482] rework backup --- src/yunohost/backup.py | 79 ++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 51aa7d6cd..4501b9078 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -752,7 +752,7 @@ class BackupManager(): for method in self.methods: logger.debug(m18n.n('backup_applying_method_' + method.method_name)) - method.mount_and_backup(self) + method.mount_and_backup() logger.debug(m18n.n('backup_method_' + method.method_name + '_finished')) def _compute_backup_size(self): @@ -851,7 +851,7 @@ class RestoreManager(): self.info = backup_info(name, with_details=True) self.archive_path = self.info['path'] self.name = name - self.method = BackupMethod.create(method) + self.method = BackupMethod.create(method, self) self.targets = BackupRestoreTargetsManager() # @@ -956,6 +956,9 @@ class RestoreManager(): # These are the hooks on the current installation available_restore_system_hooks = hook_list("restore")["hooks"] + custom_restore_hook_folder = os.path.join(CUSTOM_HOOK_FOLDER, 'restore') + filesystem.mkdir(custom_restore_hook_folder, 755, parents=True, force=True) + for system_part in target_list: # By default, we'll use the restore hooks on the current install # if available @@ -967,24 +970,23 @@ class RestoreManager(): continue # Otherwise, attempt to find it (or them?) in the archive - hook_paths = '{:s}/hooks/restore/*-{:s}'.format(self.work_dir, system_part) - hook_paths = glob(hook_paths) # If we didn't find it, we ain't gonna be able to restore it - if len(hook_paths) == 0: + if system_part not in self.info['system'] or len(self.info['system'][system_part]['paths']) == 0: logger.exception(m18n.n('restore_hook_unavailable', part=system_part)) self.targets.set_result("system", system_part, "Skipped") continue + hook_paths = self.info['system'][system_part]['paths'] + hook_paths = [ 'hooks/restore/%s' % os.path.basename(p) for p in hook_paths ] + # Otherwise, add it from the archive to the system # FIXME: Refactor hook_add and use it instead - custom_restore_hook_folder = os.path.join(CUSTOM_HOOK_FOLDER, 'restore') - filesystem.mkdir(custom_restore_hook_folder, 755, True) for hook_path in hook_paths: logger.debug("Adding restoration script '%s' to the system " "from the backup archive '%s'", hook_path, self.archive_path) - shutil.copy(hook_path, custom_restore_hook_folder) + self.method.copy(hook_path, custom_restore_hook_folder) def set_apps_targets(self, apps=[]): """ @@ -1044,7 +1046,7 @@ class RestoreManager(): filesystem.mkdir(self.work_dir, parents=True) - self.method.mount(self) + self.method.mount() self._read_info_files() @@ -1499,19 +1501,19 @@ class BackupMethod(object): method_name Public methods: - mount_and_backup(self, backup_manager) - mount(self, restore_manager) + mount_and_backup(self) + mount(self) create(cls, method, **kwargs) Usage: method = BackupMethod.create("tar") - method.mount_and_backup(backup_manager) + method.mount_and_backup() #or method = BackupMethod.create("copy") method.mount(restore_manager) """ - def __init__(self, repo=None): + def __init__(self, manager, repo=None): """ BackupMethod constructors @@ -1524,6 +1526,7 @@ class BackupMethod(object): BackupRepository object. If None, the default repo is used : /home/yunohost.backup/archives/ """ + self.manager = manager self.repo = ARCHIVES_PATH if repo is None else repo @property @@ -1569,18 +1572,13 @@ class BackupMethod(object): """ return False - def mount_and_backup(self, backup_manager): + def mount_and_backup(self): """ Run the backup on files listed by the BackupManager instance This method shouldn't be overrided, prefer overriding self.backup() and self.clean() - - Args: - backup_manager -- (BackupManager) A backup manager instance that has - already done the files collection step. """ - self.manager = backup_manager if self.need_mount(): self._organize_files() @@ -1589,17 +1587,13 @@ class BackupMethod(object): finally: self.clean() - def mount(self, restore_manager): + def mount(self): """ Mount the archive from RestoreManager instance in the working directory This method should be extended. - - Args: - restore_manager -- (RestoreManager) A restore manager instance - contains an archive to restore. """ - self.manager = restore_manager + pass def clean(self): """ @@ -1781,8 +1775,8 @@ class CopyBackupMethod(BackupMethod): could be the inverse for restoring """ - def __init__(self, repo=None): - super(CopyBackupMethod, self).__init__(repo) + def __init__(self, manager, repo=None): + super(CopyBackupMethod, self).__init__(manager, repo) @property def method_name(self): @@ -1836,6 +1830,9 @@ class CopyBackupMethod(BackupMethod): "&&", "umount", "-R", self.work_dir]) raise YunohostError('backup_cant_mount_uncompress_archive') + def copy(self, file, target): + shutil.copy(file, target) + class TarBackupMethod(BackupMethod): @@ -1843,8 +1840,8 @@ class TarBackupMethod(BackupMethod): This class compress all files to backup in archive. """ - def __init__(self, repo=None): - super(TarBackupMethod, self).__init__(repo) + def __init__(self, manager, repo=None): + super(TarBackupMethod, self).__init__(manager, repo) @property def method_name(self): @@ -1904,7 +1901,7 @@ class TarBackupMethod(BackupMethod): if not os.path.isfile(link): os.symlink(self._archive_file, link) - def mount(self, restore_manager): + def mount(self): """ Mount the archive. We avoid copy to be able to restore on system without too many space. @@ -1914,7 +1911,7 @@ class TarBackupMethod(BackupMethod): backup_archive_corrupted -- Raised if the archive appears corrupted backup_archive_cant_retrieve_info_json -- If the info.json file can't be retrieved """ - super(TarBackupMethod, self).mount(restore_manager) + super(TarBackupMethod, self).mount() # Check the archive can be open try: @@ -1994,6 +1991,11 @@ class TarBackupMethod(BackupMethod): # FIXME : Don't we want to close the tar archive here or at some point ? + def copy(self, file, target): + tar = tarfile.open(self._archive_file, "r:gz") + tar.extract(file, path=target) + tar.close() + class BorgBackupMethod(BackupMethod): @@ -2011,6 +2013,9 @@ class BorgBackupMethod(BackupMethod): def mount(self, mnt_path): raise YunohostError('backup_borg_not_implemented') + def copy(self, file, target): + raise YunohostError('backup_borg_not_implemented') + class CustomBackupMethod(BackupMethod): @@ -2020,8 +2025,8 @@ class CustomBackupMethod(BackupMethod): /etc/yunohost/hooks.d/backup_method/ """ - def __init__(self, repo=None, method=None, **kwargs): - super(CustomBackupMethod, self).__init__(repo) + def __init__(self, manager, repo=None, method=None, **kwargs): + super(CustomBackupMethod, self).__init__(manager, repo) self.args = kwargs self.method = method self._need_mount = None @@ -2062,14 +2067,14 @@ class CustomBackupMethod(BackupMethod): if ret_failed: raise YunohostError('backup_custom_backup_error') - def mount(self, restore_manager): + def mount(self): """ Launch a custom script to mount the custom archive Exceptions: backup_custom_mount_error -- Raised if the custom script failed """ - super(CustomBackupMethod, self).mount(restore_manager) + super(CustomBackupMethod, self).mount() ret = hook_callback('backup_method', [self.method], args=self._get_args('mount')) @@ -2160,9 +2165,9 @@ def backup_create(name=None, description=None, methods=[], # Add backup methods if output_directory: - methods = BackupMethod.create(methods, output_directory) + methods = BackupMethod.create(methods, backup_manager, output_directory) else: - methods = BackupMethod.create(methods) + methods = BackupMethod.create(methods, backup_manager) for method in methods: backup_manager.add(method) From 7de8417fb2262c38c1ff945f5d47aa981571f177 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 13 Apr 2020 00:07:54 +0200 Subject: [PATCH 031/482] update comments + fix mock --- src/yunohost/backup.py | 37 ++++++++++-------------- src/yunohost/tests/test_backuprestore.py | 3 +- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 4501b9078..db689125d 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -219,8 +219,8 @@ class BackupManager(): backup_manager = BackupManager(name="mybackup", description="bkp things") # Add backup method to apply - backup_manager.add(BackupMethod.create('copy','/mnt/local_fs')) - backup_manager.add(BackupMethod.create('tar','/mnt/remote_fs')) + backup_manager.add(BackupMethod.create('copy', backup_manager, '/mnt/local_fs')) + backup_manager.add(BackupMethod.create('tar', backup_manager, '/mnt/remote_fs')) # Define targets to be backuped backup_manager.set_system_targets(["data"]) @@ -972,7 +972,9 @@ class RestoreManager(): # Otherwise, attempt to find it (or them?) in the archive # If we didn't find it, we ain't gonna be able to restore it - if system_part not in self.info['system'] or len(self.info['system'][system_part]['paths']) == 0: + if system_part not in self.info['system'] or\ + 'paths' not in self.info['system'][system_part] or\ + len(self.info['system'][system_part]['paths']) == 0: logger.exception(m18n.n('restore_hook_unavailable', part=system_part)) self.targets.set_result("system", system_part, "Skipped") continue @@ -1506,11 +1508,11 @@ class BackupMethod(object): create(cls, method, **kwargs) Usage: - method = BackupMethod.create("tar") + method = BackupMethod.create("tar", backup_manager) method.mount_and_backup() #or - method = BackupMethod.create("copy") - method.mount(restore_manager) + method = BackupMethod.create("copy", restore_manager) + method.mount() """ def __init__(self, manager, repo=None): @@ -1738,7 +1740,7 @@ class BackupMethod(object): shutil.copy(path['source'], dest) @classmethod - def create(cls, method, *args): + def create(cls, method, manager, *args): """ Factory method to create instance of BackupMethod @@ -1754,7 +1756,7 @@ class BackupMethod(object): if not isinstance(method, basestring): methods = [] for m in method: - methods.append(BackupMethod.create(m, *args)) + methods.append(BackupMethod.create(m, manager, *args)) return methods bm_class = { @@ -1763,9 +1765,9 @@ class BackupMethod(object): 'borg': BorgBackupMethod } if method in ["copy", "tar", "borg"]: - return bm_class[method](*args) + return bm_class[method](manager, *args) else: - return CustomBackupMethod(method=method, *args) + return CustomBackupMethod(manager, method=method, *args) class CopyBackupMethod(BackupMethod): @@ -1913,7 +1915,8 @@ class TarBackupMethod(BackupMethod): """ super(TarBackupMethod, self).mount() - # Check the archive can be open + # Mount the tarball + logger.debug(m18n.n("restore_extracting")) try: tar = tarfile.open(self._archive_file, "r:gz") except: @@ -1926,15 +1929,7 @@ class TarBackupMethod(BackupMethod): except IOError as e: raise YunohostError("backup_archive_corrupted", archive=self._archive_file, error=str(e)) - # FIXME : Is this really useful to close the archive just to - # reopen it right after this with the same options ...? - tar.close() - - # Mount the tarball - logger.debug(m18n.n("restore_extracting")) - tar = tarfile.open(self._archive_file, "r:gz") - - if "info.json" in files_in_archive: + if "info.json" in tar.getnames(): leading_dot = "" tar.extract('info.json', path=self.work_dir) elif "./info.json" in files_in_archive: @@ -1989,7 +1984,7 @@ class TarBackupMethod(BackupMethod): ] tar.extractall(members=subdir_and_files, path=self.work_dir) - # FIXME : Don't we want to close the tar archive here or at some point ? + tar.close() def copy(self, file, target): tar = tarfile.open(self._archive_file, "r:gz") diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index c7a4f9016..d016fb529 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -593,8 +593,7 @@ def test_restore_archive_with_bad_archive(mocker): def test_backup_binds_are_readonly(mocker, monkeypatch): - def custom_mount_and_backup(self, backup_manager): - self.manager = backup_manager + def custom_mount_and_backup(self): self._organize_files() confssh = os.path.join(self.work_dir, "conf/ssh") From 5901cb9993e8d6dde51c532fa8c9ca24994e3b86 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Tue, 28 Apr 2020 21:05:36 +0200 Subject: [PATCH 032/482] remove the path of the tarfile --- src/yunohost/backup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index db689125d..65659c302 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1988,7 +1988,10 @@ class TarBackupMethod(BackupMethod): def copy(self, file, target): tar = tarfile.open(self._archive_file, "r:gz") - tar.extract(file, path=target) + file_to_extract = tar.getmember(file) + # Remove the path + file_to_extract.name = os.path.basename(file_to_extract.name) + tar.extract(file_to_extract, path=target) tar.close() From fd5ba7b1e50b47b45adec14a0913399063c33dec Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 29 Apr 2020 11:18:01 +0200 Subject: [PATCH 033/482] test custom hooks --- src/yunohost/tests/test_backuprestore.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index d016fb529..790d27d6c 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -11,6 +11,7 @@ from yunohost.backup import backup_create, backup_restore, backup_list, backup_i from yunohost.domain import _get_maindomain from yunohost.user import user_permission_list, user_create, user_list, user_delete from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps +from yunohost.hook import CUSTOM_HOOK_FOLDER # Get main domain maindomain = "" @@ -591,6 +592,27 @@ def test_restore_archive_with_bad_archive(mocker): clean_tmp_backup_directory() +def test_restore_archive_with_custom_hook(mocker): + + custom_restore_hook_folder = os.path.join(CUSTOM_HOOK_FOLDER, 'restore') + os.system("touch %s/99-yolo" % custom_restore_hook_folder) + + # Backup with custom hook system + with message(mocker, "backup_created"): + backup_create(system=[], apps=None) + archives = backup_list()["archives"] + assert len(archives) == 1 + + # Restore system with custom hook + with message(mocker, "restore_complete"): + backup_restore(name=backup_list()["archives"][0], + system=[], + apps=None, + force=True) + + os.system("rm %s/99-yolo" % custom_restore_hook_folder) + + def test_backup_binds_are_readonly(mocker, monkeypatch): def custom_mount_and_backup(self): From fc07468051b831286661905b4882f7ac36af356e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 2 May 2020 06:08:54 +0200 Subject: [PATCH 034/482] Simplify / optimize reading version of yunohost packages... --- debian/control | 2 +- .../0003_migrate_to_stretch.py | 4 +- src/yunohost/utils/packages.py | 89 +++++-------------- 3 files changed, 27 insertions(+), 68 deletions(-) diff --git a/debian/control b/debian/control index 5061ad4f2..8274197ae 100644 --- a/debian/control +++ b/debian/control @@ -13,7 +13,7 @@ Architecture: all Depends: ${python:Depends}, ${misc:Depends} , moulinette (>= 3.7), ssowat (>= 3.7) , python-psutil, python-requests, python-dnspython, python-openssl - , python-apt, python-miniupnpc, python-dbus, python-jinja2 + , python-miniupnpc, python-dbus, python-jinja2 , python-toml , apt, apt-transport-https , nginx, nginx-extras (>=1.6.2) diff --git a/src/yunohost/data_migrations/0003_migrate_to_stretch.py b/src/yunohost/data_migrations/0003_migrate_to_stretch.py index 60b26169a..e916b1ae8 100644 --- a/src/yunohost/data_migrations/0003_migrate_to_stretch.py +++ b/src/yunohost/data_migrations/0003_migrate_to_stretch.py @@ -14,7 +14,7 @@ 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.packages import get_ynh_package_version from yunohost.utils.network import get_network_interfaces from yunohost.firewall import firewall_allow, firewall_disallow @@ -94,7 +94,7 @@ class MyMigration(Migration): return int(check_output("grep VERSION_ID /etc/os-release | head -n 1 | tr '\"' ' ' | cut -d ' ' -f2")) def yunohost_major_version(self): - return int(get_installed_version("yunohost").split('.')[0]) + return int(get_ynh_package_version("yunohost")["version"].split('.')[0]) def check_assertions(self): diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index debba70f4..23da08129 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -21,15 +21,12 @@ import re import os import logging -from collections import OrderedDict -import apt -from apt_pkg import version_compare - -from moulinette import m18n +from moulinette.utils.process import check_output logger = logging.getLogger('yunohost.utils.packages') +YUNOHOST_PACKAGES = ['yunohost', 'yunohost-admin', 'moulinette', 'ssowat'] # Exceptions ----------------------------------------------------------------- @@ -368,66 +365,29 @@ class SpecifierSet(object): # Packages and cache helpers ------------------------------------------------- -def get_installed_version(*pkgnames, **kwargs): - """Get the installed version of package(s) +def get_ynh_package_version(package): - Retrieve one or more packages named `pkgnames` and return their installed - version as a dict or as a string if only one is requested. + # Returns the installed version and release version ('stable' or 'testing' + # or 'unstable') - """ - versions = OrderedDict() - cache = apt.Cache() - - # Retrieve options - with_repo = kwargs.get('with_repo', False) - - for pkgname in pkgnames: - try: - pkg = cache[pkgname] - except KeyError: - logger.warning(m18n.n('package_unknown', pkgname=pkgname)) - if with_repo: - versions[pkgname] = { - "version": None, - "repo": None, - } - else: - versions[pkgname] = None - continue - - try: - version = pkg.installed.version - except AttributeError: - version = None - - try: - # stable, testing, unstable - repo = pkg.installed.origins[0].component - except AttributeError: - repo = "" - - if repo == "now": - repo = "local" - - if with_repo: - versions[pkgname] = { - "version": version, - # when we don't have component it's because it's from a local - # install or from an image (like in vagrant) - "repo": repo if repo else "local", - } - else: - versions[pkgname] = version - - if len(pkgnames) == 1: - return versions[pkgnames[0]] - return versions + # NB: this is designed for yunohost packages only ! + # Not tested for any arbitrary packages that + # may handle changelog differently ! + changelog = "/usr/share/doc/%s/changelog.gz" % package + cmd = "gzip -cd %s | head -n1" % changelog + if not os.path.exists(changelog): + return {"version": "?", "repo": "?"} + out = check_output(cmd).split() + # Output looks like : "yunohost (1.2.3) testing; urgency=medium" + return {"version": out[1].strip("()"), + "repo": out[2].strip(";")} def meets_version_specifier(pkgname, specifier): """Check if a package installed version meets specifier""" - spec = SpecifierSet(specifier) - return get_installed_version(pkgname) in spec + # In practice, this function is only used to check the yunohost version installed + assert pkgname in YUNOHOST_PACKAGES + return get_ynh_package_version(pkgname) in SpecifierSet(specifier) # YunoHost related methods --------------------------------------------------- @@ -437,10 +397,11 @@ def ynh_packages_version(*args, **kwargs): # (Namespace(_callbacks=deque([]), _tid='_global', _to_return={}), []) {} # they don't seem to serve any purpose """Return the version of each YunoHost package""" - return get_installed_version( - 'yunohost', 'yunohost-admin', 'moulinette', 'ssowat', - with_repo=True - ) + from collections import OrderedDict + packages = OrderedDict() + for package in YUNOHOST_PACKAGES: + packages[package] = get_ynh_package_version(package) + return packages def dpkg_is_broken(): @@ -457,8 +418,6 @@ def dpkg_lock_available(): def _list_upgradable_apt_packages(): - from moulinette.utils.process import check_output - # 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") From 5c6b4118b7d41f2fb2b25034eb92fb825d4d255c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sat, 2 May 2020 11:20:27 +0200 Subject: [PATCH 035/482] Add comment about dependances --- src/yunohost/app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 2ab729a37..f3af1daa0 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -156,6 +156,9 @@ def app_info(app, full=False): def _app_upgradable(app_infos): + # python-pkg-resources contains the packaging module + # yunohost depends of python-jinja2 and python-jinja2 depends of python-pkg-resources + # so packaging module should be available on all yunohost instances from packaging import version # Determine upgradability @@ -434,6 +437,9 @@ def app_upgrade(app=[], url=None, file=None, force=False): url -- Git url to fetch for upgrade """ + # python-pkg-resources contains the packaging module + # yunohost depends of python-jinja2 and python-jinja2 depends of python-pkg-resources + # so packaging module should be available on all yunohost instances from packaging import version from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.permission import permission_sync_to_user From 0b1103faf85019696d0e4bf5b660c5303f7fac2f Mon Sep 17 00:00:00 2001 From: Marco Cirillo Date: Sun, 3 May 2020 16:40:24 +0200 Subject: [PATCH 036/482] mod_storage_ldap: change :users() to :nodes(). That reflects changes to storagemanager API in 3.14.0. --- lib/metronome/modules/mod_storage_ldap.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/metronome/modules/mod_storage_ldap.lua b/lib/metronome/modules/mod_storage_ldap.lua index 83fb4d003..87092382c 100644 --- a/lib/metronome/modules/mod_storage_ldap.lua +++ b/lib/metronome/modules/mod_storage_ldap.lua @@ -228,7 +228,7 @@ function driver:stores(username, type, pattern) return nil, "not implemented"; end -function driver:store_exists(username, datastore, type) +function driver:store_exists(username, type) return nil, "not implemented"; end @@ -236,7 +236,7 @@ function driver:purge(username) return nil, "not implemented"; end -function driver:users() +function driver:nodes(type) return nil, "not implemented"; end From 9c0ccd0b4f8a6f0820165faa96bbcad3dc0c7630 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 3 May 2020 19:46:21 +0200 Subject: [PATCH 037/482] That whole thing about Specifier is completely overengineered, let's have a more simple check for version requirements... --- debian/control | 2 +- src/yunohost/utils/packages.py | 376 +++------------------------------ 2 files changed, 35 insertions(+), 343 deletions(-) diff --git a/debian/control b/debian/control index 8274197ae..5070b2050 100644 --- a/debian/control +++ b/debian/control @@ -14,7 +14,7 @@ Depends: ${python:Depends}, ${misc:Depends} , moulinette (>= 3.7), ssowat (>= 3.7) , python-psutil, python-requests, python-dnspython, python-openssl , python-miniupnpc, python-dbus, python-jinja2 - , python-toml + , python-toml, python-packaging , apt, apt-transport-https , nginx, nginx-extras (>=1.6.2) , php-fpm, php-ldap, php-intl diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 23da08129..3f352f288 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -23,347 +23,12 @@ import os import logging from moulinette.utils.process import check_output +from packaging import version logger = logging.getLogger('yunohost.utils.packages') YUNOHOST_PACKAGES = ['yunohost', 'yunohost-admin', 'moulinette', 'ssowat'] -# Exceptions ----------------------------------------------------------------- - -class InvalidSpecifier(ValueError): - - """An invalid specifier was found.""" - - -# Version specifier ---------------------------------------------------------- -# The packaging package has been a nice inspiration for the following classes. -# See: https://github.com/pypa/packaging - -class Specifier(object): - - """Unique package version specifier - - Restrict a package version according to the `spec`. It must be a string - containing a relation from the list below followed by a version number - value. The relations allowed are, as defined by the Debian Policy Manual: - - - `<<` for strictly lower - - `<=` for lower or equal - - `=` for exactly equal - - `>=` for greater or equal - - `>>` for strictly greater - - """ - _regex_str = ( - r""" - (?P(<<|<=|=|>=|>>)) - \s* - (?P[^,;\s)]*) - """ - ) - _regex = re.compile( - r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) - - _relations = { - "<<": "lower_than", - "<=": "lower_or_equal_than", - "=": "equal", - ">=": "greater_or_equal_than", - ">>": "greater_than", - } - - def __init__(self, spec): - if isinstance(spec, basestring): - match = self._regex.search(spec) - if not match: - raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) - - self._spec = ( - match.group("relation").strip(), - match.group("version").strip(), - ) - elif isinstance(spec, self.__class__): - self._spec = spec._spec - else: - return NotImplemented - - def __repr__(self): - return "".format(str(self)) - - def __str__(self): - return "{0}{1}".format(*self._spec) - - def __hash__(self): - return hash(self._spec) - - def __eq__(self, other): - if isinstance(other, basestring): - try: - other = self.__class__(other) - except InvalidSpecifier: - return NotImplemented - elif not isinstance(other, self.__class__): - return NotImplemented - - return self._spec == other._spec - - def __ne__(self, other): - if isinstance(other, basestring): - try: - other = self.__class__(other) - except InvalidSpecifier: - return NotImplemented - elif not isinstance(other, self.__class__): - return NotImplemented - - return self._spec != other._spec - - def __and__(self, other): - return self.intersection(other) - - def __or__(self, other): - return self.union(other) - - def _get_relation(self, op): - return getattr(self, "_compare_{0}".format(self._relations[op])) - - def _compare_lower_than(self, version, spec): - return version_compare(version, spec) < 0 - - def _compare_lower_or_equal_than(self, version, spec): - return version_compare(version, spec) <= 0 - - def _compare_equal(self, version, spec): - return version_compare(version, spec) == 0 - - def _compare_greater_or_equal_than(self, version, spec): - return version_compare(version, spec) >= 0 - - def _compare_greater_than(self, version, spec): - return version_compare(version, spec) > 0 - - @property - def relation(self): - return self._spec[0] - - @property - def version(self): - return self._spec[1] - - def __contains__(self, item): - return self.contains(item) - - def intersection(self, other): - """Make the intersection of two specifiers - - Return a new `SpecifierSet` with version specifier(s) common to the - specifier and the other. - - Example: - >>> Specifier('>= 2.2') & '>> 2.2.1' == '>> 2.2.1' - >>> Specifier('>= 2.2') & '<< 2.3' == '>= 2.2, << 2.3' - - """ - if isinstance(other, basestring): - try: - other = self.__class__(other) - except InvalidSpecifier: - return NotImplemented - elif not isinstance(other, self.__class__): - return NotImplemented - - # store spec parts for easy access - rel1, v1 = self.relation, self.version - rel2, v2 = other.relation, other.version - result = [] - - if other == self: - result = [other] - elif rel1 == '=': - result = [self] if v1 in other else None - elif rel2 == '=': - result = [other] if v2 in self else None - elif v1 == v2: - result = [other if rel1[1] == '=' else self] - elif v2 in self or v1 in other: - is_self_greater = version_compare(v1, v2) > 0 - if rel1[0] == rel2[0]: - if rel1[0] == '>': - result = [self if is_self_greater else other] - else: - result = [other if is_self_greater else self] - else: - result = [self, other] - return SpecifierSet(result if result is not None else '') - - def union(self, other): - """Make the union of two version specifiers - - Return a new `SpecifierSet` with version specifiers from the - specifier and the other. - - Example: - >>> Specifier('>= 2.2') | '<< 2.3' == '>= 2.2, << 2.3' - - """ - if isinstance(other, basestring): - try: - other = self.__class__(other) - except InvalidSpecifier: - return NotImplemented - elif not isinstance(other, self.__class__): - return NotImplemented - - return SpecifierSet([self, other]) - - def contains(self, item): - """Check if the specifier contains an other - - Return whether the item is contained in the version specifier. - - Example: - >>> '2.2.1' in Specifier('<< 2.3') - >>> '2.4' not in Specifier('<< 2.3') - - """ - return self._get_relation(self.relation)(item, self.version) - - -class SpecifierSet(object): - - """A set of package version specifiers - - Combine several Specifier separated by a comma. It allows to restrict - more precisely a package version. Each package version specifier must be - meet. Note than an empty set of specifiers will always be meet. - - """ - - def __init__(self, specifiers): - if isinstance(specifiers, basestring): - specifiers = [s.strip() for s in specifiers.split(",") - if s.strip()] - - parsed = set() - for specifier in specifiers: - parsed.add(Specifier(specifier)) - - self._specs = frozenset(parsed) - - def __repr__(self): - return "".format(str(self)) - - def __str__(self): - return ",".join(sorted(str(s) for s in self._specs)) - - def __hash__(self): - return hash(self._specs) - - def __and__(self, other): - return self.intersection(other) - - def __or__(self, other): - return self.union(other) - - def __eq__(self, other): - if isinstance(other, basestring): - other = SpecifierSet(other) - elif isinstance(other, Specifier): - other = SpecifierSet(str(other)) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - return self._specs == other._specs - - def __ne__(self, other): - if isinstance(other, basestring): - other = SpecifierSet(other) - elif isinstance(other, Specifier): - other = SpecifierSet(str(other)) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - return self._specs != other._specs - - def __len__(self): - return len(self._specs) - - def __iter__(self): - return iter(self._specs) - - def __contains__(self, item): - return self.contains(item) - - def intersection(self, other): - """Make the intersection of two specifiers sets - - Return a new `SpecifierSet` with version specifier(s) common to the - set and the other. - - Example: - >>> SpecifierSet('>= 2.2') & '>> 2.2.1' == '>> 2.2.1' - >>> SpecifierSet('>= 2.2, << 2.4') & '<< 2.3' == '>= 2.2, << 2.3' - >>> SpecifierSet('>= 2.2, << 2.3') & '>= 2.4' == '' - - """ - if isinstance(other, basestring): - other = SpecifierSet(other) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - specifiers = set(self._specs | other._specs) - intersection = [specifiers.pop()] if specifiers else [] - - for specifier in specifiers: - parsed = set() - for spec in intersection: - inter = spec & specifier - if not inter: - parsed.clear() - break - # TODO: validate with other specs in parsed - parsed.update(inter._specs) - intersection = parsed - if not intersection: - break - return SpecifierSet(intersection) - - def union(self, other): - """Make the union of two specifiers sets - - Return a new `SpecifierSet` with version specifiers from the set - and the other. - - Example: - >>> SpecifierSet('>= 2.2') | '<< 2.3' == '>= 2.2, << 2.3' - - """ - if isinstance(other, basestring): - other = SpecifierSet(other) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - specifiers = SpecifierSet([]) - specifiers._specs = frozenset(self._specs | other._specs) - return specifiers - - def contains(self, item): - """Check if the set contains a version specifier - - Return whether the item is contained in all version specifiers. - - Example: - >>> '2.2.1' in SpecifierSet('>= 2.2, << 2.3') - >>> '2.4' not in SpecifierSet('>= 2.2, << 2.3') - - """ - return all( - s.contains(item) - for s in self._specs - ) - - -# Packages and cache helpers ------------------------------------------------- def get_ynh_package_version(package): @@ -383,14 +48,39 @@ def get_ynh_package_version(package): return {"version": out[1].strip("()"), "repo": out[2].strip(";")} -def meets_version_specifier(pkgname, specifier): - """Check if a package installed version meets specifier""" - # In practice, this function is only used to check the yunohost version installed - assert pkgname in YUNOHOST_PACKAGES - return get_ynh_package_version(pkgname) in SpecifierSet(specifier) +def meets_version_specifier(pkg_name, specifier): + """ + Check if a package installed version meets specifier + + specifier is something like ">> 1.2.3" + """ + + # In practice, this function is only used to check the yunohost version + # installed. + # We'll trim any ~foobar in the current installed version because it's not + # handled correctly by version.parse, but we don't care so much in that + # context + assert pkg_name in YUNOHOST_PACKAGES + pkg_version = get_ynh_package_version(pkg_name)["version"] + pkg_version = pkg_version.split("~")[0] + pkg_version = version.parse(pkg_version) + + # Extract operator and version specifier + op, req_version = re.search(r'(<<|<=|=|>=|>>) *([\d\.]+)', specifier).groups() + req_version = version.parse(req_version) + + # cmp is a python builtin that returns (-1, 0, 1) depending on comparison + deb_operators = { + "<<": lambda v1, v2: cmp(v1, v2) in [-1], + "<=": lambda v1, v2: cmp(v1, v2) in [-1, 0], + "=": lambda v1, v2: cmp(v1, v2) in [0], + ">=": lambda v1, v2: cmp(v1, v2) in [0, 1], + ">>": lambda v1, v2: cmp(v1, v2) in [1] + } + + return deb_operators[op](pkg_version, req_version) -# YunoHost related methods --------------------------------------------------- def ynh_packages_version(*args, **kwargs): # from cli the received arguments are: @@ -413,9 +103,11 @@ def dpkg_is_broken(): return any(re.match("^[0-9]+$", f) for f in os.listdir("/var/lib/dpkg/updates/")) + def dpkg_lock_available(): return os.system("lsof /var/lib/dpkg/lock >/dev/null") != 0 + def _list_upgradable_apt_packages(): # List upgradable packages From 5d811e2b39b987944d68732fa4a06674186440b6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 3 May 2020 19:52:12 +0200 Subject: [PATCH 038/482] Remove stale string --- locales/en.json | 1 - 1 file changed, 1 deletion(-) diff --git a/locales/en.json b/locales/en.json index 25712e8cd..652a602f7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -486,7 +486,6 @@ "no_internet_connection": "The server is not connected to the Internet", "not_enough_disk_space": "Not enough free space on '{path:s}'", "operation_interrupted": "The operation was manually interrupted?", - "package_unknown": "Unknown package '{pkgname}'", "packages_upgrade_failed": "Could not upgrade all the packages", "password_listed": "This password is among the most used passwords in the world. Please choose something more unique.", "password_too_simple_1": "The password needs to be at least 8 characters long", From 8d2bde84ec749770e50575780ee8a979bfb80a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josu=C3=A9=20Tille?= Date: Sun, 3 May 2020 19:56:14 +0200 Subject: [PATCH 039/482] Install python-packaging as dependance --- debian/control | 2 +- src/yunohost/app.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/debian/control b/debian/control index 5061ad4f2..db448f405 100644 --- a/debian/control +++ b/debian/control @@ -13,7 +13,7 @@ Architecture: all Depends: ${python:Depends}, ${misc:Depends} , moulinette (>= 3.7), ssowat (>= 3.7) , python-psutil, python-requests, python-dnspython, python-openssl - , python-apt, python-miniupnpc, python-dbus, python-jinja2 + , python-apt, python-miniupnpc, python-dbus, python-jinja2, python-packaging, , python-toml , apt, apt-transport-https , nginx, nginx-extras (>=1.6.2) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 452b4f7c4..edee217ce 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -183,9 +183,6 @@ def app_info(app, full=False): def _app_upgradable(app_infos): - # python-pkg-resources contains the packaging module - # yunohost depends of python-jinja2 and python-jinja2 depends of python-pkg-resources - # so packaging module should be available on all yunohost instances from packaging import version # Determine upgradability @@ -467,9 +464,6 @@ def app_upgrade(app=[], url=None, file=None, force=False): url -- Git url to fetch for upgrade """ - # python-pkg-resources contains the packaging module - # yunohost depends of python-jinja2 and python-jinja2 depends of python-pkg-resources - # so packaging module should be available on all yunohost instances from packaging import version from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback from yunohost.permission import permission_sync_to_user From 82c4357421de8a422fc3ba7df5eaa639f2ee5990 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 4 May 2020 14:00:22 +0200 Subject: [PATCH 040/482] [fix] handle new auto restart of ldap in moulinette --- src/yunohost/utils/ldap.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/yunohost/utils/ldap.py b/src/yunohost/utils/ldap.py index fd984ce56..b1f49e287 100644 --- a/src/yunohost/utils/ldap.py +++ b/src/yunohost/utils/ldap.py @@ -21,6 +21,7 @@ import os import atexit +from moulinette.core import MoulinetteLdapIsDownError from moulinette.authenticators import ldap from yunohost.utils.error import YunohostError @@ -34,8 +35,6 @@ def _get_ldap_interface(): if _ldap_interface is None: - assert_slapd_is_running() - conf = { "vendor": "ldap", "name": "as-root", "parameters": { 'uri': 'ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi', @@ -44,7 +43,12 @@ def _get_ldap_interface(): "extra": {} } - _ldap_interface = ldap.Authenticator(**conf) + try: + _ldap_interface = ldap.Authenticator(**conf) + except MoulinetteLdapIsDownError: + raise YunohostError("Service slapd is not running but is required to perform this action ... You can try to investigate what's happening with 'systemctl status slapd'") + + assert_slapd_is_running() return _ldap_interface From cf57b77d6a03f48a5f6be461c76c8570806368bf Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 4 May 2020 18:28:05 +0200 Subject: [PATCH 041/482] [fix] multi instance upgrade --- src/yunohost/app.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e2df6ba78..c5feaf452 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -172,7 +172,7 @@ def app_info(app, full=False): ret["manifest"] = local_manifest ret['settings'] = settings - absolute_app_name = app if "__" not in app else app[:app.index('__')] # idk this is the name of the app even for multiinstance apps (so wordpress__2 -> wordpress) + absolute_app_name, _ = _parse_app_instance_name(app) ret["from_catalog"] = _load_apps_catalog()["apps"].get(absolute_app_name, {}) ret['upgradable'] = _app_upgradable(ret) ret['supports_change_url'] = os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "change_url")) @@ -2177,12 +2177,14 @@ def _fetch_app_from_git(app): else: app_dict = _load_apps_catalog()["apps"] - if app not in app_dict: + app_id, _ = _parse_app_instance_name(app) + + if app_id not in app_dict: raise YunohostError('app_unknown') - elif 'git' not in app_dict[app]: + elif 'git' not in app_dict[app_id]: raise YunohostError('app_unsupported_remote_type') - app_info = app_dict[app] + app_info = app_dict[app_id] app_info['manifest']['lastUpdate'] = app_info['lastUpdate'] manifest = app_info['manifest'] url = app_info['git']['url'] From a11654e0cfb4cb72ddb6514d39eaa7782c608e18 Mon Sep 17 00:00:00 2001 From: Kayou Date: Wed, 6 May 2020 11:57:28 +0200 Subject: [PATCH 042/482] [fix] domain remove if an app without a domain is installed --- src/yunohost/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index f1dcefba9..0c1e58e54 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -180,7 +180,7 @@ def domain_remove(operation_logger, domain, force=False): # Check if apps are installed on the domain app_settings = [_get_app_settings(app) for app in _installed_apps()] - if any(s["domain"] == domain for s in app_settings): + if any("domain" in s and s["domain"] == domain for s in app_settings): raise YunohostError('domain_uninstall_app_first') operation_logger.start() From 199258166e36a0d79de20b6db6581711af5c6acd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 18:12:55 +0200 Subject: [PATCH 043/482] services[name] -> service --- src/yunohost/service.py | 42 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index c17eb04c2..aec754bd4 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -61,27 +61,27 @@ def service_add(name, description=None, log=None, log_type="file", test_status=N """ services = _get_services() - services[name] = {} + services[name] = service = {} if log is not None: if not isinstance(log, list): log = [log] - services[name]['log'] = log + service['log'] = log if not isinstance(log_type, list): log_type = [log_type] 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 + log_type.extend([log_type[-1]] * (len(log) - len(log_type))) # extend list to have the same size as log if len(log_type) == len(log): - services[name]['log_type'] = log_type + service['log_type'] = log_type else: raise YunohostError('service_add_failed', service=name) if description: - services[name]['description'] = description + service['description'] = description else: # Try to get the description from systemd service out = subprocess.check_output("systemctl show %s | grep '^Description='" % name, shell=True).strip() @@ -92,23 +92,23 @@ def service_add(name, description=None, log=None, log_type="file", test_status=N if out == name + ".service": logger.warning("/!\\ Packager ! You added a custom service without specifying a description. Please add a proper Description in the systemd configuration, or use --description to explain what the service does in a similar fashion to existing services.") else: - services[name]['description'] = out + service['description'] = out if need_lock: - services[name]['need_lock'] = True + service['need_lock'] = True if test_status: - services[name]["test_status"] = test_status + service["test_status"] = test_status if test_conf: - services[name]["test_conf"] = test_conf + service["test_conf"] = test_conf if needs_exposed_ports: - services[name]["needs_exposed_ports"] = needs_exposed_ports + service["needs_exposed_ports"] = needs_exposed_ports try: _save_services(services) - except: + except Exception: # we'll get a logger.warning with more details in _save_services raise YunohostError('service_add_failed', service=name) @@ -288,6 +288,8 @@ def service_status(names=[]): if check_names and name not in services.keys(): raise YunohostError('service_unknown', service=name) + service = services[name] + # this "service" isn't a service actually so we skip it # # the historical reason is because regenconf has been hacked into the @@ -296,10 +298,10 @@ def service_status(names=[]): # the hack was to add fake services... # we need to extract regenconf from service at some point, also because # some app would really like to use it - if services[name].get("status", "") is None: + if service.get("status", "") is None: continue - systemd_service = services[name].get("actual_systemd_service", name) + systemd_service = service.get("actual_systemd_service", name) status = _get_service_information_from_systemd(systemd_service) if status is None: @@ -314,8 +316,8 @@ def service_status(names=[]): else: translation_key = "service_description_%s" % name - if "description" in services[name] is not None: - description = services[name].get("description") + if "description" in service is not None: + description = service.get("description") else: description = m18n.n(translation_key) @@ -336,7 +338,7 @@ def service_status(names=[]): # 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]["start_on_boot"] == "generated": - result[name]["start_on_boot"] = "enabled" if glob("/etc/rc[S5].d/S??"+name) else "disabled" + result[name]["start_on_boot"] = "enabled" if glob("/etc/rc[S5].d/S??" + name) else "disabled" elif os.path.exists("/etc/systemd/system/multi-user.target.wants/%s.service" % name): result[name]["start_on_boot"] = "enabled" @@ -344,8 +346,8 @@ def service_status(names=[]): result[name]['last_state_change'] = datetime.utcfromtimestamp(status["StateChangeTimestamp"] / 1000000) # 'test_status' is an optional field to test the status of the service using a custom command - if "test_status" in services[name]: - p = subprocess.Popen(services[name]["test_status"], + if "test_status" in service: + p = subprocess.Popen(service["test_status"], shell=True, executable='/bin/bash', stdout=subprocess.PIPE, @@ -356,8 +358,8 @@ def service_status(names=[]): result[name]["status"] = "running" if p.returncode == 0 else "failed" # 'test_status' is an optional field to test the status of the service using a custom command - if "test_conf" in services[name]: - p = subprocess.Popen(services[name]["test_conf"], + if "test_conf" in service: + p = subprocess.Popen(service["test_conf"], shell=True, executable='/bin/bash', stdout=subprocess.PIPE, From 95dd1e2707e9504e114b33f6586a6ba925866f3c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 18:20:03 +0200 Subject: [PATCH 044/482] service -> infos ... + misc small syntax improvements --- src/yunohost/service.py | 53 ++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index aec754bd4..00dfaab1f 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -125,14 +125,13 @@ def service_remove(name): """ services = _get_services() - try: - del services[name] - except KeyError: + if name not in services: raise YunohostError('service_unknown', service=name) + del services[name] try: _save_services(services) - except: + except Exception: # we'll get a logger.warning with more details in _save_services raise YunohostError('service_remove_failed', service=name) @@ -275,20 +274,24 @@ def service_status(names=[]): """ services = _get_services() - check_names = True + + # If function was called with a specific list of service + if names != []: + # If user wanna check the status of a single service + if isinstance(names, str): + names = [names] + + # Validate service names requested + for name in names: + if name not in services.keys(): + raise YunohostError('service_unknown', service=name) + + # Filter only requested servivces + services = {k: v for k, v in services.items() if k in names} + result = {} - if isinstance(names, str): - names = [names] - elif len(names) == 0: - names = services.keys() - check_names = False - - for name in names: - if check_names and name not in services.keys(): - raise YunohostError('service_unknown', service=name) - - service = services[name] + for name, infos in services.items(): # this "service" isn't a service actually so we skip it # @@ -298,10 +301,10 @@ def service_status(names=[]): # the hack was to add fake services... # we need to extract regenconf from service at some point, also because # some app would really like to use it - if service.get("status", "") is None: + if infos.get("status", "") is None: continue - systemd_service = service.get("actual_systemd_service", name) + systemd_service = infos.get("actual_systemd_service", name) status = _get_service_information_from_systemd(systemd_service) if status is None: @@ -316,8 +319,8 @@ def service_status(names=[]): else: translation_key = "service_description_%s" % name - if "description" in service is not None: - description = service.get("description") + if "description" in infos is not None: + description = infos.get("description") else: description = m18n.n(translation_key) @@ -346,8 +349,8 @@ def service_status(names=[]): result[name]['last_state_change'] = datetime.utcfromtimestamp(status["StateChangeTimestamp"] / 1000000) # 'test_status' is an optional field to test the status of the service using a custom command - if "test_status" in service: - p = subprocess.Popen(service["test_status"], + if "test_status" in infos: + p = subprocess.Popen(infos["test_status"], shell=True, executable='/bin/bash', stdout=subprocess.PIPE, @@ -358,8 +361,8 @@ def service_status(names=[]): result[name]["status"] = "running" if p.returncode == 0 else "failed" # 'test_status' is an optional field to test the status of the service using a custom command - if "test_conf" in service: - p = subprocess.Popen(service["test_conf"], + if "test_conf" in infos: + p = subprocess.Popen(infos["test_conf"], shell=True, executable='/bin/bash', stdout=subprocess.PIPE, @@ -422,7 +425,7 @@ def service_log(name, number=50): 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))) + log_type_list.extend(["file"] * (len(log_list) - len(log_type_list))) result = {} From e74f49f0016f1c99fa006cc80a7b1d4dbf630266 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 18:46:44 +0200 Subject: [PATCH 045/482] Simplify log list management because log type is deprecated now that we always fetch journalctl --- src/yunohost/service.py | 61 ++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 00dfaab1f..1fe65c102 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -44,7 +44,7 @@ MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock" logger = getActionLogger('yunohost.service') -def service_add(name, description=None, log=None, log_type="file", test_status=None, test_conf=None, needs_exposed_ports=None, need_lock=False, status=None): +def service_add(name, description=None, log=None, log_type=None, test_status=None, test_conf=None, needs_exposed_ports=None, need_lock=False, status=None): """ Add a custom service @@ -52,7 +52,7 @@ def service_add(name, description=None, log=None, log_type="file", test_status=N name -- Service name to add description -- description of the service log -- Absolute path to log file to display - log_type -- Specify if the corresponding log is a file or a systemd log + log_type -- (deprecated) Specify if the corresponding log is a file or a systemd log test_status -- Specify a custom bash command to check the status of the service. N.B. : it only makes sense to specify this if the corresponding systemd service does not return the proper information. test_conf -- Specify a custom bash command to check if the configuration of the service is valid or broken, similar to nginx -t. needs_exposed_ports -- A list of ports that needs to be publicly exposed for the service to work as intended. @@ -67,19 +67,14 @@ def service_add(name, description=None, log=None, log_type="file", test_status=N if not isinstance(log, list): log = [log] + # Deprecated log_type stuff + if log_type is not None: + logger.warning("/!\\ Packagers! --log_type is deprecated. You do not need to specify --log_type systemd anymore ... Yunohost now automatically fetch the journalctl of the systemd service by default.") + # Usually when adding such a service, the service name will be provided so we remove it as it's not a log file path + log.remove(name) + service['log'] = log - if not isinstance(log_type, list): - log_type = [log_type] - - 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(log_type) == len(log): - service['log_type'] = log_type - else: - raise YunohostError('service_add_failed', service=name) - if description: service['description'] = description else: @@ -420,41 +415,33 @@ def service_log(name, number=50): raise YunohostError('service_unknown', service=name) log_list = services[name].get('log', []) - 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))) + # Legacy stuff related to --log_type where we'll typically have the service + # name in the log list but it's not an actual logfile. Nowadays journalctl + # is automatically fetch as well as regular log files. + log_list.remove(name) result = {} # First we always add the logs from journalctl / systemd result["journalctl"] = _get_journalctl_logs(name, number).splitlines() - for index, log_path in enumerate(log_list): - log_type = log_type_list[index] + for log_path in log_list: + # log is a file, read it + if not os.path.isdir(log_path): + result[log_path] = _tail(log_path, number) if os.path.exists(log_path) else [] + continue - if log_type == "file": - # log is a file, read it - if not os.path.isdir(log_path): - result[log_path] = _tail(log_path, number) if os.path.exists(log_path) else [] + 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 - 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 + if not log_file.endswith(".log"): + continue - if not log_file.endswith(".log"): - continue - - result[log_file_path] = _tail(log_file_path, number) if os.path.exists(log_file_path) else [] - else: - # N.B. : this is legacy code that can probably be removed ... to be confirmed - # get log with journalctl - result[log_path] = _get_journalctl_logs(log_path, number).splitlines() + result[log_file_path] = _tail(log_file_path, number) if os.path.exists(log_file_path) else [] return result From 6fc5b413025c18f2da79af0acbfe917581e92b4a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 18:53:06 +0200 Subject: [PATCH 046/482] Add a few tests for services add/remove/status ... --- .gitlab-ci.yml | 6 ++ src/yunohost/tests/test_service.py | 97 ++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/yunohost/tests/test_service.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ac3584630..3ebbaecd5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -92,6 +92,12 @@ test-regenconf: - cd src/yunohost - py.test tests/test_regenconf.py +test-service: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_service.py + ######################################## # LINTER ######################################## diff --git a/src/yunohost/tests/test_service.py b/src/yunohost/tests/test_service.py new file mode 100644 index 000000000..d8660c1e5 --- /dev/null +++ b/src/yunohost/tests/test_service.py @@ -0,0 +1,97 @@ +import os + +from conftest import message, raiseYunohostError + +from yunohost.service import _get_services, _save_services, service_status, service_add, service_remove + + +def setup_function(function): + + clean() + + +def teardown_function(function): + + clean() + + +def clean(): + + # To run these tests, we assume ssh(d) service exists and is running + assert os.system("pgrep sshd >/dev/null") == 0 + + services = _get_services() + assert "ssh" in services + + if "dummyservice" in services: + del services["dummyservice"] + _save_services(services) + + +def test_service_status_all(): + + status = service_status() + assert "ssh" in status.keys() + assert status["ssh"]["status"] == "running" + + +def test_service_status_single(): + + status = service_status("ssh") + assert "status" in status.keys() + assert status["status"] == "running" + + +def test_service_status_unknown_service(mocker): + + with raiseYunohostError(mocker, 'service_unknown'): + service_status(["ssh", "doesnotexists"]) + + +def test_service_add(): + + service_add("dummyservice", description="A dummy service to run tests") + assert "dummyservice" in service_status().keys() + + +def test_service_remove(): + + service_add("dummyservice", description="A dummy service to run tests") + assert "dummyservice" in service_status().keys() + service_remove("dummyservice") + assert "dummyservice" not in service_status().keys() + + +def test_service_remove_service_that_doesnt_exists(mocker): + + assert "dummyservice" not in service_status().keys() + + with raiseYunohostError(mocker, 'service_unknown'): + service_remove("dummyservice") + + assert "dummyservice" not in service_status().keys() + + +def test_service_update_to_add_properties(): + + service_add("dummyservice", description="") + assert not _get_services()["dummyservice"].get("test_status") + service_add("dummyservice", description="", test_status="true") + assert _get_services()["dummyservice"].get("test_status") == "true" + + +def test_service_update_to_change_properties(): + + service_add("dummyservice", description="", test_status="false") + assert _get_services()["dummyservice"].get("test_status") == "false" + service_add("dummyservice", description="", test_status="true") + assert _get_services()["dummyservice"].get("test_status") == "true" + + +def test_service_update_to_remove_properties(): + + service_add("dummyservice", description="", test_status="false") + assert _get_services()["dummyservice"].get("test_status") == "false" + service_add("dummyservice", description="", test_status="") + assert not _get_services()["dummyservice"].get("test_status") + From c721aaf258d96d86518d6ba04dc29ba0535cb3b0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 19:01:07 +0200 Subject: [PATCH 047/482] version was not defined... --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e2df6ba78..ffc1de378 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2331,6 +2331,7 @@ def _check_manifest_requirements(manifest, app_instance_name): # Iterate over requirements for pkgname, spec in requirements.items(): if not packages.meets_version_specifier(pkgname, spec): + version = packages.ynh_packages_version()[pkgname]["version"] raise YunohostError('app_requirements_unmeet', pkgname=pkgname, version=version, spec=spec, app=app_instance_name) From 582a63bc0cf12fe9de36b927101cdeef5229ddec Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 19:45:00 +0200 Subject: [PATCH 048/482] Add at least a check to detect epic python errors --- .gitlab-ci.yml | 5 +++++ tox.ini | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ac3584630..4d4bd798a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -117,6 +117,11 @@ lint: script: - tox -e lint +invalidcode: + extends: .lint-stage + script: + - tox -e invalidcode + # Disabled, waiting for buster #format-check: # extends: .lint-stage diff --git a/tox.ini b/tox.ini index ac109609c..8d033367b 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ skip_install=True deps = pytest >= 4.6.3, < 5.0 pyyaml >= 5.1.2, < 6.0 + flake8 >= 3.7.9, < 3.8 commands = pytest {posargs} @@ -16,3 +17,8 @@ commands = skip_install=True commands = flake8 src doc data tests deps = flake8 + +[testenv:invalidcode] +skip_install=True +commands = flake8 src data --exclude src/yunohost/tests --select F --ignore F401,F841 +deps = flake8 From 40eaec605e3d8f31fd2e45b829217a0f6b3f7e0b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 19:45:16 +0200 Subject: [PATCH 049/482] Make flake8 happy (c.f. previous commit) --- src/yunohost/app.py | 4 ++-- src/yunohost/domain.py | 8 ++++---- src/yunohost/service.py | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index ffc1de378..8e1d55671 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -926,12 +926,12 @@ def dump_app_log_extract_for_debugging(operation_logger): r"ynh_script_progression" ] - filters = [re.compile(f) for f in filters] + filters = [re.compile(f_) for f_ in filters] lines_to_display = [] for line in lines: - if not ": " in line.strip(): + if ": " not in line.strip(): continue # A line typically looks like diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index f1dcefba9..b63a269c6 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -528,10 +528,10 @@ def _build_dns_conf(domain, ttl=3600, include_empty_AAAA_if_no_ipv6=False): #################### 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], + "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], } ################## diff --git a/src/yunohost/service.py b/src/yunohost/service.py index c17eb04c2..029ecf77c 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -26,7 +26,6 @@ import re import os -import re import time import yaml import subprocess From 58a29f218e93b5537d7ac0858f909395a3c9fd19 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 20:06:16 +0200 Subject: [PATCH 050/482] Update src/yunohost/service.py Co-authored-by: Kayou --- src/yunohost/service.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 1fe65c102..a818d9fbd 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -314,9 +314,8 @@ def service_status(names=[]): else: translation_key = "service_description_%s" % name - if "description" in infos is not None: - description = infos.get("description") - else: + description = infos.get("description") + if not description: description = m18n.n(translation_key) # that mean that we don't have a translation for this string From f25e07fd829cd179393762d5c23a6f9f2670ed1c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 20:18:49 +0200 Subject: [PATCH 051/482] Update src/yunohost/service.py --- src/yunohost/service.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index a818d9fbd..4a86043b3 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -427,8 +427,11 @@ 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): - result[log_path] = _tail(log_path, number) if os.path.exists(log_path) else [] + if os.path.isfile(log_path): + result[log_path] = _tail(log_path, number) + continue + elif not os.path.isdir(log_path): + result[log_path] = [] continue for log_file in os.listdir(log_path): From 9e86014636902ec5267c1f44692a26066b087718 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 6 May 2020 20:20:38 +0200 Subject: [PATCH 052/482] [mod] improve error message when apps are still installed on a domain --- locales/en.json | 2 +- src/yunohost/domain.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index 25712e8cd..5d49baf23 100644 --- a/locales/en.json +++ b/locales/en.json @@ -271,7 +271,7 @@ "domain_dyndns_root_unknown": "Unknown DynDNS root domain", "domain_exists": "The domain already exists", "domain_hostname_failed": "Could not set new hostname. This might cause an issue later (it might be fine).", - "domain_uninstall_app_first": "One or more apps are installed on this domain. Please uninstall them before proceeding to domain removal", + "domain_uninstall_app_first": "Those applications are still installed on your domain: {apps}. Please uninstall them before proceeding to domain removal", "domain_unknown": "Unknown domain", "domains_available": "Available domains:", "done": "Done", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 0c1e58e54..2440d8702 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -179,9 +179,15 @@ def domain_remove(operation_logger, domain, force=False): raise YunohostError('domain_cannot_remove_main_add_new_one', domain=domain) # Check if apps are installed on the domain - app_settings = [_get_app_settings(app) for app in _installed_apps()] - if any("domain" in s and s["domain"] == domain for s in app_settings): - raise YunohostError('domain_uninstall_app_first') + apps_on_that_domain = [] + + for app in _installed_apps(): + settings = _get_app_settings(app) + if settings.get("domain") == domain: + apps_on_that_domain.append("%s (on https://%s%s)" % (app, domain, settings.get("path"))) + + if apps_on_that_domain: + raise YunohostError('domain_uninstall_app_first', apps=", ".join(apps_on_that_domain)) operation_logger.start() ldap = _get_ldap_interface() From 0b035782d07a8c25c355eee8a20cfd2b6842e608 Mon Sep 17 00:00:00 2001 From: Bram Date: Wed, 6 May 2020 20:35:32 +0200 Subject: [PATCH 053/482] Update src/yunohost/domain.py Co-authored-by: Alexandre Aubin --- src/yunohost/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 2440d8702..700505d54 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -184,7 +184,7 @@ def domain_remove(operation_logger, domain, force=False): for app in _installed_apps(): settings = _get_app_settings(app) if settings.get("domain") == domain: - apps_on_that_domain.append("%s (on https://%s%s)" % (app, domain, settings.get("path"))) + apps_on_that_domain.append("%s (on https://%s%s)" % (app, domain, settings["path"]) if "path" in settings else app) if apps_on_that_domain: raise YunohostError('domain_uninstall_app_first', apps=", ".join(apps_on_that_domain)) From c7dd8817740a7af4bf2b267eb7fc3d6667775857 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 21:41:54 +0200 Subject: [PATCH 054/482] Default 'ask' questions for common app manifest args --- locales/en.json | 5 ++++ src/yunohost/app.py | 59 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/locales/en.json b/locales/en.json index 25712e8cd..486bc053c 100644 --- a/locales/en.json +++ b/locales/en.json @@ -27,6 +27,11 @@ "app_make_default_location_already_used": "Can't make the app '{app}' the default on the domain, '{domain}' is already in use by the other app '{other_app}'", "app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps:s}", "app_manifest_invalid": "Something is wrong with the app manifest: {error}", + "app_manifest_install_ask_domain": "Choose the domain where this app should be installed", + "app_manifest_install_ask_path": "Choose the path where this app should be installed", + "app_manifest_install_ask_password": "Choose an administration password for this app", + "app_manifest_install_ask_admin": "Choose an administrator user for this app", + "app_manifest_install_ask_is_public": "Should this app be exposed to anonymous visitors?", "app_not_upgraded": "The app '{failed_app}' failed to upgrade, and as a consequence the following apps' upgrades have been cancelled: {apps}", "app_not_correctly_installed": "{app:s} seems to be incorrectly installed", "app_not_installed": "Could not find the app '{app:s}' in the list of installed apps: {all_apps}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e2df6ba78..4a42a0484 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -91,6 +91,8 @@ def app_catalog(full=False, with_categories=False): "description": infos['manifest']['description'], "level": infos["level"], } + else: + infos["manifest"]["arguments"] = _set_default_ask_questions(infos["manifest"]["arguments"]) # Trim info for categories if not using --full for category in catalog["categories"]: @@ -110,7 +112,6 @@ def app_catalog(full=False, with_categories=False): return {"apps": catalog["apps"], "categories": catalog["categories"]} - # Old legacy function... def app_fetchlist(): logger.warning("'yunohost app fetchlist' is deprecated. Please use 'yunohost tools update --apps' instead") @@ -170,6 +171,7 @@ def app_info(app, full=False): return ret ret["manifest"] = local_manifest + ret["manifest"]["arguments"] = _set_default_ask_questions(ret["manifest"]["arguments"]) ret['settings'] = settings absolute_app_name = app if "__" not in app else app[:app.index('__')] # idk this is the name of the app even for multiinstance apps (so wordpress__2 -> wordpress) @@ -2071,12 +2073,63 @@ def _get_manifest_of_app(path): manifest["arguments"]["install"] = install_arguments - return manifest elif os.path.exists(os.path.join(path, "manifest.json")): - return read_json(os.path.join(path, "manifest.json")) + manifest = read_json(os.path.join(path, "manifest.json")) else: raise YunohostError("There doesn't seem to be any manifest file in %s ... It looks like an app was not correctly installed/removed." % path, raw_msg=True) + manifest["arguments"] = _set_default_ask_questions(manifest["arguments"]) + return manifest + + +def _set_default_ask_questions(arguments): + + # arguments is something like + # { "install": [ + # { "name": "domain", + # "type": "domain", + # .... + # }, + # { "name": "path", + # "type": "path" + # ... + # }, + # ... + # ], + # "upgrade": [ ... ] + # } + + # We set a default for any question with these matching (type, name) + # type namei + # N.B. : this is only for install script ... should be reworked for other + # scripts if we supports args for other scripts in the future... + questions_with_default = [("domain", "domain"), + ("path", "path"), + ("password", "password"), + ("user", "admin"), + ("boolean", "is_public")] + + for script_name, arg_list in arguments.items(): + + # We only support questions for install so far, and for other + if script_name != "install": + continue + + for arg in arg_list: + + # Do not override 'ask' field if provided by app ?... Or shall we ? + #if "ask" in arg: + # continue + + # If this arg corresponds to a question with default ask message... + if any((arg.get("type"), arg["name"]) == question for question in questions_with_default): + # The key is for example "app_manifest_install_ask_domain" + key = "app_manifest_%s_ask_%s" % (script_name, arg["name"]) + arg["ask"] = m18n.n(key) + + return arguments + + def _get_git_last_commit_hash(repository, reference='HEAD'): """ From 49c4324ee1af0c02f927afe3d326c0aca961b147 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 22:17:39 +0200 Subject: [PATCH 055/482] During app installs, set default answer for user-type args to main user --- src/yunohost/app.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e2df6ba78..289a03bd2 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2439,9 +2439,16 @@ def _parse_args_in_yunohost_format(args, action_args): elif arg_type == 'user': msignals.display(m18n.n('users_available')) - for user in user_list()['users'].keys(): + users = user_list()['users'] + for user in users.keys(): msignals.display("- {}".format(user)) + root_mail = "root@%s" % _get_maindomain() + for user in users.keys(): + if root_mail in user_info(user)["mail-aliases"]: + arg_default = user + ask_string += ' (default: {0})'.format(arg_default) + elif arg_type == 'password': msignals.display(m18n.n('good_practices_about_user_password')) From 882b003bd70718a181347971d1262d6167e25082 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 May 2020 00:07:10 +0200 Subject: [PATCH 056/482] Fix i18n string test --- src/yunohost/app.py | 13 ++++++------- tests/test_i18n_keys.py | 4 ++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 4a42a0484..0647e17d4 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2103,11 +2103,11 @@ def _set_default_ask_questions(arguments): # type namei # N.B. : this is only for install script ... should be reworked for other # scripts if we supports args for other scripts in the future... - questions_with_default = [("domain", "domain"), - ("path", "path"), - ("password", "password"), - ("user", "admin"), - ("boolean", "is_public")] + questions_with_default = [("domain", "domain"), # i18n: app_manifest_install_ask_domain + ("path", "path"), # i18n: app_manifest_install_ask_path + ("password", "password"), # i18n: app_manifest_install_ask_password + ("user", "admin"), # i18n: app_manifest_install_ask_admin + ("boolean", "is_public")] # i18n: app_manifest_install_ask_is_public for script_name, arg_list in arguments.items(): @@ -2118,7 +2118,7 @@ def _set_default_ask_questions(arguments): for arg in arg_list: # Do not override 'ask' field if provided by app ?... Or shall we ? - #if "ask" in arg: + # if "ask" in arg: # continue # If this arg corresponds to a question with default ask message... @@ -2130,7 +2130,6 @@ def _set_default_ask_questions(arguments): return arguments - def _get_git_last_commit_hash(repository, reference='HEAD'): """ Attempt to retrieve the last commit hash of a git repository diff --git a/tests/test_i18n_keys.py b/tests/test_i18n_keys.py index 874794e11..799db3de2 100644 --- a/tests/test_i18n_keys.py +++ b/tests/test_i18n_keys.py @@ -23,8 +23,10 @@ def find_expected_string_keys(): # Try to find : # m18n.n( "foo" # YunohostError("foo" + # # i18n: foo p1 = re.compile(r'm18n\.n\(\s*[\"\'](\w+)[\"\']') p2 = re.compile(r'YunohostError\([\'\"](\w+)[\'\"]') + p3 = re.compile(r'# i18n: [\'\"]?(\w+)[\'\"]?') python_files = glob.glob("src/yunohost/*.py") python_files.extend(glob.glob("src/yunohost/utils/*.py")) @@ -42,6 +44,8 @@ def find_expected_string_keys(): if m.endswith("_"): continue yield m + for m in p3.findall(content): + yield m # For each diagnosis, try to find strings like "diagnosis_stuff_foo" (c.f. diagnosis summaries) # Also we expect to have "diagnosis_description_" for each diagnosis From f202e8d4b80e3a8471c179ed0ddd1da74f1ae4fa Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 May 2020 04:10:32 +0200 Subject: [PATCH 057/482] Enforce metronome >= 3.14.0 --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 5061ad4f2..bc31d6211 100644 --- a/debian/control +++ b/debian/control @@ -27,7 +27,7 @@ Depends: ${python:Depends}, ${misc:Depends} , dovecot-core, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-antispam , rspamd (>= 1.6.0), opendkim-tools, postsrsd, procmail, mailutils , redis-server - , metronome + , metronome (>=3.14.0) , git, curl, wget, cron, unzip, jq , lsb-release, haveged, fake-hwclock, equivs, lsof, whois, python-publicsuffix Recommends: yunohost-admin From 22e03dc10e2d34f45fc31ddd819db6edcfecdb49 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 May 2020 04:13:30 +0200 Subject: [PATCH 058/482] Update changelog for 3.8.3 --- debian/changelog | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/debian/changelog b/debian/changelog index c119d57e7..40109eff9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,22 @@ +yunohost (3.8.3) testing; urgency=low + + - [fix] Remove dot in reverse DNS check + - [fix] Upgrade of multi-instance apps was broken (#976) + - [fix] Check was broken if an apps with no domain setting was installed (#978) + - [enh] Add a timeout to wget (#972) + - [fix] ynh_get_ram: Enforce choosing --free or --total (#972) + - [fix] Simplify / improve robustness of backup list + - [enh] Make nodejs helpers easier to use (#939) + - [fix] Misc tweak for disk usage diagnosis, some values were inconsistent / bad UX / ... + - [enh] Assert slapd is running to avoid miserably crashing with weird ldap errors + - [enh] Try to show smarter / more useful logs by filtering irrelevant lines like set +x etc + - Technical tweaks for metronome 3.14.0 support + - Misc improvements for tests and linters + + Thanks to all contributors <3 ! (Bram, Kay0u, Maniack C., ljf, Maranda) + + -- Alexandre Aubin Thu, 07 Apr 2020 04:00:00 +0000 + yunohost (3.8.2.2) testing; urgency=low Aleks broke everything /again/ *.* From 0b24aa68d504f2ab53daacff0ad982aa9ba6b650 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 May 2020 18:08:12 +0200 Subject: [PATCH 059/482] Ugly hack to install new deps in debian/control --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7459ae982..0ce234c6b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,6 +11,7 @@ postinstall: image: before-postinstall stage: postinstall script: + - apt install --no-install-recommends -y $(cat debian/control | grep "^Depends" -A50 | grep "Recommends:" -B50 | grep "^ *," | grep -o -P "[\w\-]{3,}") - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns ######################################## @@ -129,4 +130,4 @@ lint: #format-check: # extends: .lint-stage # script: -# - black --check --diff \ No newline at end of file +# - black --check --diff From 3a62d828ba9b2b3774f606f19f8c2a6ead63bfbf Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 6 May 2020 19:01:07 +0200 Subject: [PATCH 060/482] version was not defined... --- src/yunohost/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index e2df6ba78..ffc1de378 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2331,6 +2331,7 @@ def _check_manifest_requirements(manifest, app_instance_name): # Iterate over requirements for pkgname, spec in requirements.items(): if not packages.meets_version_specifier(pkgname, spec): + version = packages.ynh_packages_version()[pkgname]["version"] raise YunohostError('app_requirements_unmeet', pkgname=pkgname, version=version, spec=spec, app=app_instance_name) From 8bcf7530811c947093f56c8c2ad63db2537d8ca8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 May 2020 18:34:51 +0200 Subject: [PATCH 061/482] Also split + and - --- src/yunohost/utils/packages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 3f352f288..6103206e5 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -63,7 +63,7 @@ def meets_version_specifier(pkg_name, specifier): # context assert pkg_name in YUNOHOST_PACKAGES pkg_version = get_ynh_package_version(pkg_name)["version"] - pkg_version = pkg_version.split("~")[0] + pkg_version = re.split(r'\~|\+|\-', pkg_version)[0] pkg_version = version.parse(pkg_version) # Extract operator and version specifier From 94d0e253f7ca56b5fb742e18716c5587d14077b1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 7 Apr 2020 20:24:04 +0200 Subject: [PATCH 062/482] Clean usr/bin/yunohost and yunohost-api ... --- bin/yunohost | 100 +++++++++++------------------------------ bin/yunohost-api | 115 +++++++++++------------------------------------ 2 files changed, 52 insertions(+), 163 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index b640c8c52..29a97e016 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -5,36 +5,13 @@ import os import sys import argparse -# Either we are in a development environment or not -IN_DEVEL = False - -# Level for which loggers will log -LOGGERS_LEVEL = 'DEBUG' -TTY_LOG_LEVEL = 'INFO' - -# Handlers that will be used by loggers -# - file: log to the file LOG_DIR/LOG_FILE -# - tty: log to current tty -LOGGERS_HANDLERS = ['file', 'tty'] - -# Directory and file to be used by logging -LOG_DIR = '/var/log/yunohost' -LOG_FILE = 'yunohost-cli.log' - -# Check and load - as needed - development environment -if not __file__.startswith('/usr/'): - IN_DEVEL = True -if IN_DEVEL: - basedir = os.path.abspath('%s/../' % os.path.dirname(__file__)) - if os.path.isdir(os.path.join(basedir, 'moulinette')): - sys.path.insert(0, basedir) - LOG_DIR = os.path.join(basedir, 'log') - - import moulinette from moulinette.actionsmap import ActionsMap from moulinette.interfaces.cli import colorize, get_locale +# Directory and file to be used by logging +LOG_DIR = '/var/log/yunohost' +LOG_FILE = 'yunohost-cli.log' # Initialization & helpers functions ----------------------------------- @@ -46,10 +23,6 @@ def _die(message, title='Error:'): def _parse_cli_args(): """Parse additional arguments for the cli""" parser = argparse.ArgumentParser(add_help=False) - parser.add_argument('--no-cache', - action='store_false', default=True, dest='use_cache', - help="Don't use actions map cache", - ) parser.add_argument('--output-as', choices=['json', 'plain', 'none'], default=None, help="Output result in another format", @@ -90,22 +63,13 @@ def _parse_cli_args(): def _init_moulinette(debug=False, quiet=False): """Configure logging and initialize the moulinette""" - # Define loggers handlers - handlers = set(LOGGERS_HANDLERS) - if quiet and 'tty' in handlers: - handlers.remove('tty') - elif 'tty' not in handlers: - handlers.append('tty') - root_handlers = set(handlers) - if not debug and 'tty' in root_handlers: - root_handlers.remove('tty') - - # Define loggers level - level = LOGGERS_LEVEL - tty_level = TTY_LOG_LEVEL - if debug: - tty_level = 'DEBUG' + # Create log directory + if not os.path.isdir(LOG_DIR): + try: + os.makedirs(LOG_DIR, 0750) + except os.error as e: + _die(str(e)) # Custom logging configuration logging = { @@ -126,7 +90,7 @@ def _init_moulinette(debug=False, quiet=False): }, 'handlers': { 'tty': { - 'level': tty_level, + 'level': 'DEBUG' if debug else 'INFO', 'class': 'moulinette.interfaces.cli.TTYHandler', 'formatter': 'tty-debug' if debug else '', }, @@ -139,45 +103,34 @@ def _init_moulinette(debug=False, quiet=False): }, 'loggers': { 'yunohost': { - 'level': level, - 'handlers': handlers, + 'level': 'DEBUG', + 'handlers': ['file', 'tty'] if not quiet else ['file'], 'propagate': False, }, 'moulinette': { - 'level': level, + 'level': 'DEBUG', 'handlers': [], 'propagate': True, }, 'moulinette.interface': { - 'level': level, - 'handlers': handlers, + 'level': 'DEBUG', + 'handlers': ['file', 'tty'] if not quiet else ['file'], 'propagate': False, }, }, 'root': { - 'level': level, - 'handlers': root_handlers, + 'level': 'DEBUG', + 'handlers': ['file', 'tty'] if debug else ['file'], }, } - # Create log directory - if not os.path.isdir(LOG_DIR): - try: - os.makedirs(LOG_DIR, 0750) - except os.error as e: - _die(str(e)) - # Initialize moulinette - moulinette.init(logging_config=logging, _from_source=IN_DEVEL) + moulinette.init(logging_config=logging) def _retrieve_namespaces(): """Return the list of namespaces to load""" - ret = ['yunohost'] - for n in ActionsMap.get_namespaces(): - # Append YunoHost modules - if n.startswith('ynh_'): - ret.append(n) - return ret + extensions = [n for n in ActionsMap.get_namespaces() if n.startswith('ynh_')] + return ['yunohost'] + extensions # Stupid PATH management because sometimes (e.g. some cron job) PATH is only /usr/bin:/bin ... default_path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" @@ -197,10 +150,9 @@ if __name__ == '__main__': _init_moulinette(opts.debug, opts.quiet) # Check that YunoHost is installed + allowed_if_not_installed = ['tools postinstall', 'backup restore', 'log display'] if not os.path.isfile('/etc/yunohost/installed') and \ - (len(args) < 2 or (args[0] +' '+ args[1] != 'tools postinstall' and \ - args[0] +' '+ args[1] != 'backup restore' and \ - args[0] +' '+ args[1] != 'log display')): + (len(args) < 2 or (args[0] +' '+ args[1] not in allowed_if_not_installed)): from moulinette import m18n # Init i18n @@ -212,9 +164,11 @@ if __name__ == '__main__': # Execute the action ret = moulinette.cli( - _retrieve_namespaces(), args, - use_cache=opts.use_cache, output_as=opts.output_as, - password=opts.password, parser_kwargs={'top_parser': parser}, + _retrieve_namespaces(), + args, + output_as=opts.output_as, + password=opts.password, timeout=opts.timeout, + parser_kwargs={'top_parser': parser}, ) sys.exit(ret) diff --git a/bin/yunohost-api b/bin/yunohost-api index e518c34b0..7503d28ad 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -5,42 +5,18 @@ import os import sys import argparse -# Either we are in a development environment or not -IN_DEVEL = False +import moulinette +from moulinette.actionsmap import ActionsMap +from moulinette.interfaces.cli import colorize # Default server configuration DEFAULT_HOST = 'localhost' DEFAULT_PORT = 6787 -# Level for which loggers will log -LOGGERS_LEVEL = 'DEBUG' -API_LOGGER_LEVEL = 'INFO' - -# Handlers that will be used by loggers -# - file: log to the file LOG_DIR/LOG_FILE -# - api: serve logs through the api -# - console: log to stderr -LOGGERS_HANDLERS = ['file', 'api'] - # Directory and file to be used by logging LOG_DIR = '/var/log/yunohost' LOG_FILE = 'yunohost-api.log' -# Check and load - as needed - development environment -if not __file__.startswith('/usr/'): - IN_DEVEL = True -if IN_DEVEL: - basedir = os.path.abspath('%s/../' % os.path.dirname(__file__)) - if os.path.isdir(os.path.join(basedir, 'moulinette')): - sys.path.insert(0, basedir) - LOG_DIR = os.path.join(basedir, 'log') - - -import moulinette -from moulinette.actionsmap import ActionsMap -from moulinette.interfaces.cli import colorize - - # Initialization & helpers functions ----------------------------------- def _die(message, title='Error:'): @@ -62,46 +38,26 @@ def _parse_api_args(): action='store', default=DEFAULT_PORT, type=int, help="Port to listen on (default: %d)" % DEFAULT_PORT, ) - srv_group.add_argument('--no-websocket', - action='store_true', default=True, dest='use_websocket', - help="Serve without WebSocket support, used to handle " - "asynchronous responses such as the messages", - ) glob_group = parser.add_argument_group('global arguments') - glob_group.add_argument('--no-cache', - action='store_false', default=True, dest='use_cache', - help="Don't use actions map cache", - ) glob_group.add_argument('--debug', action='store_true', default=False, help="Set log level to DEBUG", ) - glob_group.add_argument('--verbose', - action='store_true', default=False, - help="Be verbose in the output", - ) glob_group.add_argument('--help', action='help', help="Show this help message and exit", ) return parser.parse_args() -def _init_moulinette(use_websocket=True, debug=False, verbose=False): +def _init_moulinette(debug=False): """Configure logging and initialize the moulinette""" - # Define loggers handlers - handlers = set(LOGGERS_HANDLERS) - if not use_websocket and 'api' in handlers: - handlers.remove('api') - if verbose and 'console' not in handlers: - handlers.add('console') - root_handlers = handlers - set(['api']) - # Define loggers level - level = LOGGERS_LEVEL - api_level = API_LOGGER_LEVEL - if debug: - level = 'DEBUG' - api_level = 'DEBUG' + # Create log directory + if not os.path.isdir(LOG_DIR): + try: + os.makedirs(LOG_DIR, 0750) + except os.error as e: + _die(str(e)) # Custom logging configuration logging = { @@ -122,7 +78,7 @@ def _init_moulinette(use_websocket=True, debug=False, verbose=False): }, 'handlers': { 'api': { - 'level': api_level, + 'level': 'DEBUG' if debug else 'INFO', 'class': 'moulinette.interfaces.api.APIQueueHandler', }, 'file': { @@ -140,58 +96,36 @@ def _init_moulinette(use_websocket=True, debug=False, verbose=False): }, 'loggers': { 'yunohost': { - 'level': level, - 'handlers': handlers, + 'level': 'DEBUG', + 'handlers': ['file', 'api'] + ['console'] if debug else [], 'propagate': False, }, 'moulinette': { - 'level': level, + 'level': 'DEBUG', 'handlers': [], 'propagate': True, }, - 'gnupg': { - 'level': 'INFO', - 'handlers': [], - 'propagate': False, - }, }, 'root': { - 'level': level, - 'handlers': root_handlers, + 'level': 'DEBUG', + 'handlers': ['file'] + ['console'] if debug else [], }, } - # Create log directory - if not os.path.isdir(LOG_DIR): - try: - os.makedirs(LOG_DIR, 0750) - except os.error as e: - _die(str(e)) - # Initialize moulinette - moulinette.init(logging_config=logging, _from_source=IN_DEVEL) + moulinette.init(logging_config=logging) def _retrieve_namespaces(): """Return the list of namespaces to load""" - ret = ['yunohost'] - for n in ActionsMap.get_namespaces(): - # Append YunoHost modules - if n.startswith('ynh_'): - ret.append(n) - return ret + extensions = [n for n in ActionsMap.get_namespaces() if n.startswith('ynh_')] + return ['yunohost'] + extensions # Callbacks for additional routes -------------------------------------- def is_installed(): - """ - Check whether YunoHost is installed or not - - """ - installed = False - if os.path.isfile('/etc/yunohost/installed'): - installed = True - return { 'installed': installed } + """ Check whether YunoHost is installed or not """ + return { 'installed': os.path.isfile('/etc/yunohost/installed') } # Main action ---------------------------------------------------------- @@ -203,8 +137,9 @@ if __name__ == '__main__': # Run the server ret = moulinette.api( _retrieve_namespaces(), - host=opts.host, port=opts.port, routes={ - ('GET', '/installed'): is_installed, - }, use_cache=opts.use_cache, use_websocket=opts.use_websocket + host=opts.host, + port=opts.port, + routes={ ('GET', '/installed'): is_installed, }, + use_websocket=True ) sys.exit(ret) From 31e5f7e8b599fb25e27ebdd9b4a4e90f4a1fd62b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 8 Apr 2020 02:58:42 +0200 Subject: [PATCH 063/482] This arg ain't meaningful anymore? --- bin/yunohost | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index 29a97e016..4e0ece1e4 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -39,10 +39,6 @@ def _parse_cli_args(): type=int, default=None, help="Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock", ) - parser.add_argument('--admin-password', - default=None, dest='password', metavar='PASSWORD', - help="The admin password to use to authenticate", - ) # deprecated arguments parser.add_argument('--plain', action='store_true', default=False, help=argparse.SUPPRESS @@ -167,7 +163,6 @@ if __name__ == '__main__': _retrieve_namespaces(), args, output_as=opts.output_as, - password=opts.password, timeout=opts.timeout, parser_kwargs={'top_parser': parser}, ) From 68c9244492166c94570fcc06e4c79c75b2d5186b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Apr 2020 04:34:49 +0200 Subject: [PATCH 064/482] Remove _die, simplify creation of log dir, make linter a bit happier --- bin/yunohost | 24 ++++++++++-------------- bin/yunohost-api | 16 ++++------------ 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index 4e0ece1e4..b56666eb4 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -13,12 +13,12 @@ from moulinette.interfaces.cli import colorize, get_locale LOG_DIR = '/var/log/yunohost' LOG_FILE = 'yunohost-cli.log' +# Create log directory +if not os.path.isdir(LOG_DIR): + os.makedirs(LOG_DIR, 0750) + # Initialization & helpers functions ----------------------------------- -def _die(message, title='Error:'): - """Print error message and exit""" - print('%s %s' % (colorize(title, 'red'), message)) - sys.exit(1) def _parse_cli_args(): """Parse additional arguments for the cli""" @@ -57,16 +57,10 @@ def _parse_cli_args(): return (parser, opts, args) + def _init_moulinette(debug=False, quiet=False): """Configure logging and initialize the moulinette""" - # Create log directory - if not os.path.isdir(LOG_DIR): - try: - os.makedirs(LOG_DIR, 0750) - except os.error as e: - _die(str(e)) - # Custom logging configuration logging = { 'version': 1, @@ -123,6 +117,7 @@ def _init_moulinette(debug=False, quiet=False): # Initialize moulinette moulinette.init(logging_config=logging) + def _retrieve_namespaces(): """Return the list of namespaces to load""" extensions = [n for n in ActionsMap.get_namespaces() if n.startswith('ynh_')] @@ -138,7 +133,7 @@ if os.environ["PATH"] != default_path: if __name__ == '__main__': if os.geteuid() != 0: # since moulinette isn't initialized, we can't use m18n here - sys.stderr.write("\033[1;31mError:\033[0m yunohost command must be " \ + sys.stderr.write("\033[1;31mError:\033[0m yunohost command must be " "run as root or with sudo.\n") sys.exit(1) @@ -148,7 +143,7 @@ if __name__ == '__main__': # Check that YunoHost is installed allowed_if_not_installed = ['tools postinstall', 'backup restore', 'log display'] if not os.path.isfile('/etc/yunohost/installed') and \ - (len(args) < 2 or (args[0] +' '+ args[1] not in allowed_if_not_installed)): + (len(args) < 2 or (args[0] + ' ' + args[1] not in allowed_if_not_installed)): from moulinette import m18n # Init i18n @@ -156,7 +151,8 @@ if __name__ == '__main__': m18n.set_locale(get_locale()) # Print error and exit - _die(m18n.n('yunohost_not_installed'), m18n.g('error')) + print(colorize(m18n.g('error'), 'red') + " " + m18n.n('yunohost_not_installed')) + sys.exit(1) # Execute the action ret = moulinette.cli( diff --git a/bin/yunohost-api b/bin/yunohost-api index 7503d28ad..3185738f6 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -17,12 +17,11 @@ DEFAULT_PORT = 6787 LOG_DIR = '/var/log/yunohost' LOG_FILE = 'yunohost-api.log' -# Initialization & helpers functions ----------------------------------- +# Create log directory +if not os.path.isdir(LOG_DIR): + os.makedirs(LOG_DIR, 0750) -def _die(message, title='Error:'): - """Print error message and exit""" - print('%s %s' % (colorize(title, 'red'), message)) - sys.exit(1) +# Initialization & helpers functions ----------------------------------- def _parse_api_args(): """Parse main arguments for the api""" @@ -52,13 +51,6 @@ def _parse_api_args(): def _init_moulinette(debug=False): """Configure logging and initialize the moulinette""" - # Create log directory - if not os.path.isdir(LOG_DIR): - try: - os.makedirs(LOG_DIR, 0750) - except os.error as e: - _die(str(e)) - # Custom logging configuration logging = { 'version': 1, From f5c16737ebd44748225e7d17f60faf67c05d97db Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Apr 2020 05:15:08 +0200 Subject: [PATCH 065/482] More stuff to reduce complexity --- bin/yunohost | 7 +++++-- bin/yunohost-api | 22 ++++++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index b56666eb4..173cbc1cb 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -4,9 +4,9 @@ import os import sys import argparse +import glob import moulinette -from moulinette.actionsmap import ActionsMap from moulinette.interfaces.cli import colorize, get_locale # Directory and file to be used by logging @@ -123,6 +123,7 @@ def _retrieve_namespaces(): extensions = [n for n in ActionsMap.get_namespaces() if n.startswith('ynh_')] return ['yunohost'] + extensions + # Stupid PATH management because sometimes (e.g. some cron job) PATH is only /usr/bin:/bin ... default_path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" if os.environ["PATH"] != default_path: @@ -154,9 +155,11 @@ if __name__ == '__main__': print(colorize(m18n.g('error'), 'red') + " " + m18n.n('yunohost_not_installed')) sys.exit(1) + extensions = [f.split('/')[-1][:-4] for f in glob.glob("/usr/share/moulinette/actionsmap/ynh_*.yml")] + # Execute the action ret = moulinette.cli( - _retrieve_namespaces(), + ['yunohost'] + extensions, args, output_as=opts.output_as, timeout=opts.timeout, diff --git a/bin/yunohost-api b/bin/yunohost-api index 3185738f6..7a2119a08 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -4,10 +4,9 @@ import os import sys import argparse +import glob import moulinette -from moulinette.actionsmap import ActionsMap -from moulinette.interfaces.cli import colorize # Default server configuration DEFAULT_HOST = 'localhost' @@ -23,6 +22,7 @@ if not os.path.isdir(LOG_DIR): # Initialization & helpers functions ----------------------------------- + def _parse_api_args(): """Parse main arguments for the api""" parser = argparse.ArgumentParser(add_help=False, @@ -48,6 +48,7 @@ def _parse_api_args(): return parser.parse_args() + def _init_moulinette(debug=False): """Configure logging and initialize the moulinette""" @@ -107,31 +108,28 @@ def _init_moulinette(debug=False): # Initialize moulinette moulinette.init(logging_config=logging) -def _retrieve_namespaces(): - """Return the list of namespaces to load""" - extensions = [n for n in ActionsMap.get_namespaces() if n.startswith('ynh_')] - return ['yunohost'] + extensions - - # Callbacks for additional routes -------------------------------------- + def is_installed(): """ Check whether YunoHost is installed or not """ - return { 'installed': os.path.isfile('/etc/yunohost/installed') } + return {'installed': os.path.isfile('/etc/yunohost/installed')} # Main action ---------------------------------------------------------- if __name__ == '__main__': opts = _parse_api_args() - _init_moulinette(opts.use_websocket, opts.debug, opts.verbose) + _init_moulinette(opts.debug) + + extensions = [f.split('/')[-1][:-4] for f in glob.glob("/usr/share/moulinette/actionsmap/ynh_*.yml")] # Run the server ret = moulinette.api( - _retrieve_namespaces(), + ['yunohost'] + extensions, host=opts.host, port=opts.port, - routes={ ('GET', '/installed'): is_installed, }, + routes={('GET', '/installed'): is_installed}, use_websocket=True ) sys.exit(ret) From 233c962710aec0e98f77936bde476fe662ca062f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Apr 2020 16:49:35 +0200 Subject: [PATCH 066/482] Hmpf idk another iteration to cleaning attempt --- bin/yunohost | 26 +++++++++++--------------- bin/yunohost-api | 23 +++++++++-------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index 173cbc1cb..8c4b10d8d 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -7,19 +7,13 @@ import argparse import glob import moulinette -from moulinette.interfaces.cli import colorize, get_locale # Directory and file to be used by logging LOG_DIR = '/var/log/yunohost' LOG_FILE = 'yunohost-cli.log' -# Create log directory -if not os.path.isdir(LOG_DIR): - os.makedirs(LOG_DIR, 0750) - # Initialization & helpers functions ----------------------------------- - def _parse_cli_args(): """Parse additional arguments for the cli""" parser = argparse.ArgumentParser(add_help=False) @@ -58,11 +52,13 @@ def _parse_cli_args(): return (parser, opts, args) -def _init_moulinette(debug=False, quiet=False): - """Configure logging and initialize the moulinette""" +def init(debug=False, quiet=False, logfile='%s/%s' % (LOG_DIR, LOG_FILE)): - # Custom logging configuration - logging = { + logdir = os.path.dirname(logfile) + if not os.path.isdir(logdir): + os.makedirs(logdir, 0750) + + moulinette.init(logging_config={ 'version': 1, 'disable_existing_loggers': True, 'formatters': { @@ -87,7 +83,7 @@ def _init_moulinette(debug=False, quiet=False): 'file': { 'class': 'logging.FileHandler', 'formatter': 'precise', - 'filename': '%s/%s' % (LOG_DIR, LOG_FILE), + 'filename': logfile, 'filters': ['action'], }, }, @@ -112,10 +108,8 @@ def _init_moulinette(debug=False, quiet=False): 'level': 'DEBUG', 'handlers': ['file', 'tty'] if debug else ['file'], }, - } + }) - # Initialize moulinette - moulinette.init(logging_config=logging) def _retrieve_namespaces(): @@ -139,7 +133,7 @@ if __name__ == '__main__': sys.exit(1) parser, opts, args = _parse_cli_args() - _init_moulinette(opts.debug, opts.quiet) + init(debug=opts.debug, quiet=opts.quiet) # Check that YunoHost is installed allowed_if_not_installed = ['tools postinstall', 'backup restore', 'log display'] @@ -147,6 +141,8 @@ if __name__ == '__main__': (len(args) < 2 or (args[0] + ' ' + args[1] not in allowed_if_not_installed)): from moulinette import m18n + from moulinette.interfaces.cli import colorize, get_locale + # Init i18n m18n.load_namespace('yunohost') m18n.set_locale(get_locale()) diff --git a/bin/yunohost-api b/bin/yunohost-api index 7a2119a08..0b6961a90 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -16,10 +16,6 @@ DEFAULT_PORT = 6787 LOG_DIR = '/var/log/yunohost' LOG_FILE = 'yunohost-api.log' -# Create log directory -if not os.path.isdir(LOG_DIR): - os.makedirs(LOG_DIR, 0750) - # Initialization & helpers functions ----------------------------------- @@ -49,11 +45,13 @@ def _parse_api_args(): return parser.parse_args() -def _init_moulinette(debug=False): - """Configure logging and initialize the moulinette""" +def init_api(debug=False, logfile='%s/%s' % (LOG_DIR, LOG_FILE)): - # Custom logging configuration - logging = { + logdir = os.path.dirname(logfile) + if not os.path.isdir(logdir): + os.makedirs(logdir, 0750) + + moulinette.init(logging_config={ 'version': 1, 'disable_existing_loggers': True, 'formatters': { @@ -77,7 +75,7 @@ def _init_moulinette(debug=False): 'file': { 'class': 'logging.handlers.WatchedFileHandler', 'formatter': 'precise', - 'filename': '%s/%s' % (LOG_DIR, LOG_FILE), + 'filename': logfile, 'filters': ['action'], }, 'console': { @@ -103,10 +101,7 @@ def _init_moulinette(debug=False): 'level': 'DEBUG', 'handlers': ['file'] + ['console'] if debug else [], }, - } - - # Initialize moulinette - moulinette.init(logging_config=logging) + }) # Callbacks for additional routes -------------------------------------- @@ -120,7 +115,7 @@ def is_installed(): if __name__ == '__main__': opts = _parse_api_args() - _init_moulinette(opts.debug) + init_api(opts.debug) extensions = [f.split('/')[-1][:-4] for f in glob.glob("/usr/share/moulinette/actionsmap/ynh_*.yml")] From dee08a16feb5358122bb424f0da3458952f03e85 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Apr 2020 18:41:54 +0200 Subject: [PATCH 067/482] Move moulinette initialization and other stuff to src/yunohost/__init__.py --- bin/yunohost | 113 +++------------------- bin/yunohost-api | 92 +----------------- src/yunohost/__init__.py | 202 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 191 deletions(-) diff --git a/bin/yunohost b/bin/yunohost index 8c4b10d8d..546d2d913 100755 --- a/bin/yunohost +++ b/bin/yunohost @@ -4,34 +4,29 @@ import os import sys import argparse -import glob -import moulinette +sys.path.insert(0, "/usr/lib/moulinette/") +import yunohost -# Directory and file to be used by logging -LOG_DIR = '/var/log/yunohost' -LOG_FILE = 'yunohost-cli.log' - -# Initialization & helpers functions ----------------------------------- def _parse_cli_args(): """Parse additional arguments for the cli""" parser = argparse.ArgumentParser(add_help=False) parser.add_argument('--output-as', choices=['json', 'plain', 'none'], default=None, - help="Output result in another format", + help="Output result in another format" ) parser.add_argument('--debug', action='store_true', default=False, - help="Log and print debug messages", + help="Log and print debug messages" ) parser.add_argument('--quiet', action='store_true', default=False, - help="Don't produce any output", + help="Don't produce any output" ) parser.add_argument('--timeout', type=int, default=None, - help="Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock", + help="Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock" ) # deprecated arguments parser.add_argument('--plain', @@ -52,72 +47,6 @@ def _parse_cli_args(): return (parser, opts, args) -def init(debug=False, quiet=False, logfile='%s/%s' % (LOG_DIR, LOG_FILE)): - - logdir = os.path.dirname(logfile) - if not os.path.isdir(logdir): - os.makedirs(logdir, 0750) - - moulinette.init(logging_config={ - 'version': 1, - 'disable_existing_loggers': True, - 'formatters': { - 'tty-debug': { - 'format': '%(relativeCreated)-4d %(fmessage)s' - }, - 'precise': { - 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' - }, - }, - 'filters': { - 'action': { - '()': 'moulinette.utils.log.ActionFilter', - }, - }, - 'handlers': { - 'tty': { - 'level': 'DEBUG' if debug else 'INFO', - 'class': 'moulinette.interfaces.cli.TTYHandler', - 'formatter': 'tty-debug' if debug else '', - }, - 'file': { - 'class': 'logging.FileHandler', - 'formatter': 'precise', - 'filename': logfile, - 'filters': ['action'], - }, - }, - 'loggers': { - 'yunohost': { - 'level': 'DEBUG', - 'handlers': ['file', 'tty'] if not quiet else ['file'], - 'propagate': False, - }, - 'moulinette': { - 'level': 'DEBUG', - 'handlers': [], - 'propagate': True, - }, - 'moulinette.interface': { - 'level': 'DEBUG', - 'handlers': ['file', 'tty'] if not quiet else ['file'], - 'propagate': False, - }, - }, - 'root': { - 'level': 'DEBUG', - 'handlers': ['file', 'tty'] if debug else ['file'], - }, - }) - - - -def _retrieve_namespaces(): - """Return the list of namespaces to load""" - extensions = [n for n in ActionsMap.get_namespaces() if n.startswith('ynh_')] - return ['yunohost'] + extensions - - # Stupid PATH management because sometimes (e.g. some cron job) PATH is only /usr/bin:/bin ... default_path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" if os.environ["PATH"] != default_path: @@ -127,38 +56,18 @@ if os.environ["PATH"] != default_path: if __name__ == '__main__': if os.geteuid() != 0: - # since moulinette isn't initialized, we can't use m18n here sys.stderr.write("\033[1;31mError:\033[0m yunohost command must be " "run as root or with sudo.\n") sys.exit(1) parser, opts, args = _parse_cli_args() - init(debug=opts.debug, quiet=opts.quiet) - - # Check that YunoHost is installed - allowed_if_not_installed = ['tools postinstall', 'backup restore', 'log display'] - if not os.path.isfile('/etc/yunohost/installed') and \ - (len(args) < 2 or (args[0] + ' ' + args[1] not in allowed_if_not_installed)): - - from moulinette import m18n - from moulinette.interfaces.cli import colorize, get_locale - - # Init i18n - m18n.load_namespace('yunohost') - m18n.set_locale(get_locale()) - - # Print error and exit - print(colorize(m18n.g('error'), 'red') + " " + m18n.n('yunohost_not_installed')) - sys.exit(1) - - extensions = [f.split('/')[-1][:-4] for f in glob.glob("/usr/share/moulinette/actionsmap/ynh_*.yml")] # Execute the action - ret = moulinette.cli( - ['yunohost'] + extensions, - args, + yunohost.cli( + debug=opts.debug, + quiet=opts.quiet, output_as=opts.output_as, timeout=opts.timeout, - parser_kwargs={'top_parser': parser}, + args=args, + parser=parser ) - sys.exit(ret) diff --git a/bin/yunohost-api b/bin/yunohost-api index 0b6961a90..cc849590a 100755 --- a/bin/yunohost-api +++ b/bin/yunohost-api @@ -1,23 +1,16 @@ #! /usr/bin/python # -*- coding: utf-8 -*- -import os import sys import argparse -import glob -import moulinette +sys.path.insert(0, "/usr/lib/moulinette/") +import yunohost # Default server configuration DEFAULT_HOST = 'localhost' DEFAULT_PORT = 6787 -# Directory and file to be used by logging -LOG_DIR = '/var/log/yunohost' -LOG_FILE = 'yunohost-api.log' - -# Initialization & helpers functions ----------------------------------- - def _parse_api_args(): """Parse main arguments for the api""" @@ -45,86 +38,7 @@ def _parse_api_args(): return parser.parse_args() -def init_api(debug=False, logfile='%s/%s' % (LOG_DIR, LOG_FILE)): - - logdir = os.path.dirname(logfile) - if not os.path.isdir(logdir): - os.makedirs(logdir, 0750) - - moulinette.init(logging_config={ - 'version': 1, - 'disable_existing_loggers': True, - 'formatters': { - 'console': { - 'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' - }, - 'precise': { - 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' - }, - }, - 'filters': { - 'action': { - '()': 'moulinette.utils.log.ActionFilter', - }, - }, - 'handlers': { - 'api': { - 'level': 'DEBUG' if debug else 'INFO', - 'class': 'moulinette.interfaces.api.APIQueueHandler', - }, - 'file': { - 'class': 'logging.handlers.WatchedFileHandler', - 'formatter': 'precise', - 'filename': logfile, - 'filters': ['action'], - }, - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'console', - 'stream': 'ext://sys.stdout', - 'filters': ['action'], - }, - }, - 'loggers': { - 'yunohost': { - 'level': 'DEBUG', - 'handlers': ['file', 'api'] + ['console'] if debug else [], - 'propagate': False, - }, - 'moulinette': { - 'level': 'DEBUG', - 'handlers': [], - 'propagate': True, - }, - }, - 'root': { - 'level': 'DEBUG', - 'handlers': ['file'] + ['console'] if debug else [], - }, - }) - -# Callbacks for additional routes -------------------------------------- - - -def is_installed(): - """ Check whether YunoHost is installed or not """ - return {'installed': os.path.isfile('/etc/yunohost/installed')} - - -# Main action ---------------------------------------------------------- - if __name__ == '__main__': opts = _parse_api_args() - init_api(opts.debug) - - extensions = [f.split('/')[-1][:-4] for f in glob.glob("/usr/share/moulinette/actionsmap/ynh_*.yml")] - # Run the server - ret = moulinette.api( - ['yunohost'] + extensions, - host=opts.host, - port=opts.port, - routes={('GET', '/installed'): is_installed}, - use_websocket=True - ) - sys.exit(ret) + yunohost.api(debug=opts.debug, host=opts.host, port=opts.port) diff --git a/src/yunohost/__init__.py b/src/yunohost/__init__.py index e69de29bb..06e3d773d 100644 --- a/src/yunohost/__init__.py +++ b/src/yunohost/__init__.py @@ -0,0 +1,202 @@ +#! /usr/bin/python +# -*- coding: utf-8 -*- + +import os +import sys +import glob + +import moulinette +from moulinette.utils.log import configure_logging + + +def is_installed(): + return os.path.isfile('/etc/yunohost/installed') + + +def cli(debug, quiet, output_as, timeout, args, parser): + + init_logging(interface="cli", debug=debug, quiet=quiet) + + # Check that YunoHost is installed + if not is_installed(): + check_command_is_valid_before_postinstall(args) + + ret = moulinette.cli( + ['yunohost'] + extensions(), + args, + output_as=output_as, + timeout=timeout, + parser_kwargs={'top_parser': parser}, + ) + sys.exit(ret) + + +def api(debug, host, port): + + init_logging(debug=debug) + + def is_installed_api(): + return {'installed': is_installed()} + + # FIXME : someday, maybe find a way to disable route /postinstall if + # postinstall already done ... + + ret = moulinette.api( + ['yunohost'] + extensions(), + host=host, + port=port, + routes={('GET', '/installed'): is_installed_api}, + use_websocket=True + ) + sys.exit(ret) + + +def extensions(): + # This is probably not used anywhere, but the actionsmap and code can be + # extended by creating such files that contain bits of actionmap... + return [f.split('/')[-1][:-4] for f in glob.glob("/usr/share/moulinette/actionsmap/ynh_*.yml")] + + +def check_command_is_valid_before_postinstall(args): + + allowed_if_not_postinstalled = ['tools postinstall', + 'tools versions', + 'backup list', + 'backup restore', + 'log display'] + + if (len(args) < 2 or (args[0] + ' ' + args[1] not in allowed_if_not_postinstalled)): + + # This function is called before m18n is initialized, so we only initialized + # the specific bit to be able to call m18n.n/g()... + from moulinette import m18n + from moulinette.interfaces.cli import colorize, get_locale + + # Init i18n + m18n.load_namespace('yunohost') + m18n.set_locale(get_locale()) + + print(colorize(m18n.g('error'), 'red') + " " + m18n.n('yunohost_not_installed')) + sys.exit(1) + + +def init_logging(interface="cli", + debug=False, + quiet=False, + logdir="/var/log/yunohost"): + + logfile = os.path.join(logdir, "yunohost-%s.log" % interface) + + if not os.path.isdir(logdir): + os.makedirs(logdir, 0750) + + # ####################################################################### # + # Logging configuration for CLI (or any other interface than api...) # + # ####################################################################### # + if interface != "api": + configure_logging({ + 'version': 1, + 'disable_existing_loggers': True, + 'formatters': { + 'tty-debug': { + 'format': '%(relativeCreated)-4d %(fmessage)s' + }, + 'precise': { + 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' + }, + }, + 'filters': { + 'action': { + '()': 'moulinette.utils.log.ActionFilter', + }, + }, + 'handlers': { + 'tty': { + 'level': 'DEBUG' if debug else 'INFO', + 'class': 'moulinette.interfaces.cli.TTYHandler', + 'formatter': 'tty-debug' if debug else '', + }, + 'file': { + 'class': 'logging.FileHandler', + 'formatter': 'precise', + 'filename': logfile, + 'filters': ['action'], + }, + }, + 'loggers': { + 'yunohost': { + 'level': 'DEBUG', + 'handlers': ['file', 'tty'] if not quiet else ['file'], + 'propagate': False, + }, + 'moulinette': { + 'level': 'DEBUG', + 'handlers': [], + 'propagate': True, + }, + 'moulinette.interface': { + 'level': 'DEBUG', + 'handlers': ['file', 'tty'] if not quiet else ['file'], + 'propagate': False, + }, + }, + 'root': { + 'level': 'DEBUG', + 'handlers': ['file', 'tty'] if debug else ['file'], + }, + }) + # ####################################################################### # + # Logging configuration for API # + # ####################################################################### # + else: + configure_logging({ + 'version': 1, + 'disable_existing_loggers': True, + 'formatters': { + 'console': { + 'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' + }, + 'precise': { + 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' + }, + }, + 'filters': { + 'action': { + '()': 'moulinette.utils.log.ActionFilter', + }, + }, + 'handlers': { + 'api': { + 'level': 'DEBUG' if debug else 'INFO', + 'class': 'moulinette.interfaces.api.APIQueueHandler', + }, + 'file': { + 'class': 'logging.handlers.WatchedFileHandler', + 'formatter': 'precise', + 'filename': logfile, + 'filters': ['action'], + }, + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'console', + 'stream': 'ext://sys.stdout', + 'filters': ['action'], + }, + }, + 'loggers': { + 'yunohost': { + 'level': 'DEBUG', + 'handlers': ['file', 'api'] + ['console'] if debug else [], + 'propagate': False, + }, + 'moulinette': { + 'level': 'DEBUG', + 'handlers': [], + 'propagate': True, + }, + }, + 'root': { + 'level': 'DEBUG', + 'handlers': ['file'] + ['console'] if debug else [], + }, + }) From 69a520657c13aba8964c659963413cd8a1e6a951 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 22 Apr 2020 18:59:44 +0200 Subject: [PATCH 068/482] Add init_i18n for convenience when using yunohost as a lib... --- src/yunohost/__init__.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/yunohost/__init__.py b/src/yunohost/__init__.py index 06e3d773d..8a23f8e00 100644 --- a/src/yunohost/__init__.py +++ b/src/yunohost/__init__.py @@ -6,8 +6,9 @@ import sys import glob import moulinette +from moulinette import m18n from moulinette.utils.log import configure_logging - +from moulinette.interfaces.cli import colorize, get_locale def is_installed(): return os.path.isfile('/etc/yunohost/installed') @@ -66,20 +67,18 @@ def check_command_is_valid_before_postinstall(args): 'log display'] if (len(args) < 2 or (args[0] + ' ' + args[1] not in allowed_if_not_postinstalled)): - - # This function is called before m18n is initialized, so we only initialized - # the specific bit to be able to call m18n.n/g()... - from moulinette import m18n - from moulinette.interfaces.cli import colorize, get_locale - - # Init i18n - m18n.load_namespace('yunohost') - m18n.set_locale(get_locale()) - + init_i18n() print(colorize(m18n.g('error'), 'red') + " " + m18n.n('yunohost_not_installed')) sys.exit(1) +def init_i18n(): + # This should only be called when not willing to go through moulinette.cli + # or moulinette.api but still willing to call m18n.n/g... + m18n.load_namespace('yunohost') + m18n.set_locale(get_locale()) + + def init_logging(interface="cli", debug=False, quiet=False, From 7df8e8421df39bbed03f1fc7cb53102876a400a5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 28 Apr 2020 04:03:02 +0200 Subject: [PATCH 069/482] Forgot to set interface as api for init logging --- src/yunohost/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/__init__.py b/src/yunohost/__init__.py index 8a23f8e00..e2821f558 100644 --- a/src/yunohost/__init__.py +++ b/src/yunohost/__init__.py @@ -34,7 +34,7 @@ def cli(debug, quiet, output_as, timeout, args, parser): def api(debug, host, port): - init_logging(debug=debug) + init_logging(interface="api", debug=debug) def is_installed_api(): return {'installed': is_installed()} From 84e39a416a69c7974e36cf32af1d4e40e376bf03 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 04:43:04 +0200 Subject: [PATCH 070/482] Add an 'init' helper for scripts/tests to initialize everything needed... --- src/yunohost/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/yunohost/__init__.py b/src/yunohost/__init__.py index e2821f558..3e7ddb496 100644 --- a/src/yunohost/__init__.py +++ b/src/yunohost/__init__.py @@ -72,6 +72,19 @@ def check_command_is_valid_before_postinstall(args): sys.exit(1) +def init(interface="cli", debug=False, quiet=False, logdir="/var/log/yunohost"): + """ + This is a small util function ONLY meant to be used to initialize a Yunohost + context when ran from tests or from scripts. + """ + init_logging(interface=interface, debug=debug, quiet=quiet, logdir=logdir) + init_i18n() + from moulinette.core import MoulinetteLock + lock = MoulinetteLock("yunohost", timeout=30) + lock.acquire() + return lock + + def init_i18n(): # This should only be called when not willing to go through moulinette.cli # or moulinette.api but still willing to call m18n.n/g... From 96e115c6091317d4d77e324e4f40f9e659648e03 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 29 Apr 2020 04:43:29 +0200 Subject: [PATCH 071/482] Use 'init' helper from tests! --- src/yunohost/tests/conftest.py | 67 ++-------------------------------- 1 file changed, 4 insertions(+), 63 deletions(-) diff --git a/src/yunohost/tests/conftest.py b/src/yunohost/tests/conftest.py index 073c880f8..69f8bcfed 100644 --- a/src/yunohost/tests/conftest.py +++ b/src/yunohost/tests/conftest.py @@ -1,12 +1,11 @@ import os import pytest import sys -import moulinette +import moulinette from moulinette import m18n from yunohost.utils.error import YunohostError from contextlib import contextmanager - sys.path.append("..") @@ -68,65 +67,7 @@ moulinette.core.Moulinette18n.n = new_m18nn def pytest_cmdline_main(config): - """Configure logging and initialize the moulinette""" - # Define loggers handlers - handlers = set(['tty']) - root_handlers = set(handlers) - # Define loggers level - level = 'DEBUG' - if config.option.yunodebug: - tty_level = 'DEBUG' - else: - tty_level = 'INFO' - - # Custom logging configuration - logging = { - 'version': 1, - 'disable_existing_loggers': True, - 'formatters': { - 'tty-debug': { - 'format': '%(relativeCreated)-4d %(fmessage)s' - }, - 'precise': { - 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s' - }, - }, - 'filters': { - 'action': { - '()': 'moulinette.utils.log.ActionFilter', - }, - }, - 'handlers': { - 'tty': { - 'level': tty_level, - 'class': 'moulinette.interfaces.cli.TTYHandler', - 'formatter': '', - }, - }, - 'loggers': { - 'yunohost': { - 'level': level, - 'handlers': handlers, - 'propagate': False, - }, - 'moulinette': { - 'level': level, - 'handlers': [], - 'propagate': True, - }, - 'moulinette.interface': { - 'level': level, - 'handlers': handlers, - 'propagate': False, - }, - }, - 'root': { - 'level': level, - 'handlers': root_handlers, - }, - } - - # Initialize moulinette - moulinette.init(logging_config=logging, _from_source=False) - moulinette.m18n.load_namespace('yunohost') + sys.path.insert(0, "/usr/lib/moulinette/") + import yunohost + yunohost.init(debug=config.option.yunodebug) From d8be90165c2345ec956e24f789a7215f923679dd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 1 May 2020 06:11:53 +0200 Subject: [PATCH 072/482] Propagate changes from moulinette/simplify-interface-init --- src/yunohost/__init__.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/yunohost/__init__.py b/src/yunohost/__init__.py index 3e7ddb496..810d6127a 100644 --- a/src/yunohost/__init__.py +++ b/src/yunohost/__init__.py @@ -3,13 +3,13 @@ import os import sys -import glob import moulinette from moulinette import m18n from moulinette.utils.log import configure_logging from moulinette.interfaces.cli import colorize, get_locale + def is_installed(): return os.path.isfile('/etc/yunohost/installed') @@ -23,11 +23,10 @@ def cli(debug, quiet, output_as, timeout, args, parser): check_command_is_valid_before_postinstall(args) ret = moulinette.cli( - ['yunohost'] + extensions(), args, output_as=output_as, timeout=timeout, - parser_kwargs={'top_parser': parser}, + top_parser=parser ) sys.exit(ret) @@ -43,21 +42,13 @@ def api(debug, host, port): # postinstall already done ... ret = moulinette.api( - ['yunohost'] + extensions(), host=host, port=port, routes={('GET', '/installed'): is_installed_api}, - use_websocket=True ) sys.exit(ret) -def extensions(): - # This is probably not used anywhere, but the actionsmap and code can be - # extended by creating such files that contain bits of actionmap... - return [f.split('/')[-1][:-4] for f in glob.glob("/usr/share/moulinette/actionsmap/ynh_*.yml")] - - def check_command_is_valid_before_postinstall(args): allowed_if_not_postinstalled = ['tools postinstall', From 63ff02be5065efe845d101c29275adf2488e29fc Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 24 Apr 2020 01:11:56 +0200 Subject: [PATCH 073/482] Keep track of 'parent' operation in operation loggers --- src/yunohost/log.py | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/yunohost/log.py b/src/yunohost/log.py index de84280f0..20305b2c6 100644 --- a/src/yunohost/log.py +++ b/src/yunohost/log.py @@ -28,6 +28,7 @@ import os import re import yaml import collections +import glob from datetime import datetime from logging import FileHandler, getLogger, Formatter @@ -350,6 +351,8 @@ class OperationLogger(object): This class record logs and metadata like context or start time/end time. """ + _instances = [] + def __init__(self, operation, related_to=None, **kwargs): # TODO add a way to not save password on app installation self.operation = operation @@ -360,6 +363,8 @@ class OperationLogger(object): self.logger = None self._name = None self.data_to_redact = [] + self.parent = self.parent_logger() + self._instances.append(self) for filename in ["/etc/yunohost/mysql", "/etc/yunohost/psql"]: if os.path.exists(filename): @@ -370,6 +375,50 @@ class OperationLogger(object): if not os.path.exists(self.path): os.makedirs(self.path) + def parent_logger(self): + + # If there are other operation logger instances + for instance in reversed(self._instances): + # Is one of these operation logger started but not yet done ? + if instance.started_at is not None and instance.ended_at is None: + # We are a child of the first one we found + return instance.name + + locks = read_file("/var/run/moulinette_yunohost.lock").strip().split("\n") + # If we're the process with the lock, we're the root logger + if locks == [] or str(os.getpid()) in locks: + return None + + # If we get here, we are in a yunohost command called by a yunohost + # (maybe indirectly from an app script for example...) + # + # The strategy is : + # 1. list 20 most recent log files + # 2. iterate over the PID of parent processes + # 3. see if parent process has some log file open (being actively + # written in) + # 4. if among those file, there's an operation log file, we use the id + # of the most recent file + + recent_operation_logs = sorted(glob.iglob("/var/log/yunohost/categories/operation/*.log"), key=os.path.getctime, reverse=True)[:20] + + import psutil + proc = psutil.Process().parent() + while proc is not None: + # We use proc.open_files() to list files opened / actively used by this proc + # We only keep files matching a recent yunohost operation log + active_logs = sorted([f.path for f in proc.open_files() if f.path in recent_operation_logs], key=os.path.getctime, reverse=True) + if active_logs != []: + # extra the log if from the full path + return os.path.basename(active_logs[0])[:-4] + else: + proc = proc.parent() + continue + + # If nothing found, assume we're the root operation logger + return None + + def start(self): """ Start to record logs that change the system @@ -456,6 +505,7 @@ class OperationLogger(object): data = { 'started_at': self.started_at, 'operation': self.operation, + 'parent': self.parent, } if self.related_to is not None: data['related_to'] = self.related_to @@ -491,8 +541,10 @@ class OperationLogger(object): self.ended_at = datetime.utcnow() self._error = error self._success = error is None + if self.logger is not None: self.logger.removeHandler(self.file_handler) + self.file_handler.close() is_api = msettings.get('interface') == 'api' desc = _get_description_from_name(self.name) From 2f31cb6463c51a0cf6965f86dc5e3733aa3f5962 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 7 May 2020 22:37:57 +0200 Subject: [PATCH 074/482] Make sure to handle symlinks when fetching logfiles --- src/yunohost/service.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 4a86043b3..f905d3906 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -426,6 +426,13 @@ def service_log(name, number=50): result["journalctl"] = _get_journalctl_logs(name, number).splitlines() for log_path in log_list: + + if not os.path.exists(log_path): + continue + + # Make sure to resolve symlinks + log_path = os.path.realpath(log_path) + # log is a file, read it if os.path.isfile(log_path): result[log_path] = _tail(log_path, number) From bcb16416b2c259b04ef97da74ed5b141209911a3 Mon Sep 17 00:00:00 2001 From: Augustin Trancart Date: Fri, 8 May 2020 17:59:46 +0200 Subject: [PATCH 075/482] Remove default value for deprecated log_type args The service_add method check if the argument is empty, but what it really wants to do is checking if the args is not systemd (as far as I understand). As this value is deprecated, better remove the default to fix this logic. --- data/actionsmap/yunohost.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index a748e4533..e2b4447cf 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -996,7 +996,6 @@ service: choices: - file - systemd - default: file --test_status: help: Specify a custom bash command to check the status of the service. Note that it only makes sense to specify this if the corresponding systemd service does not return the proper information already. --test_conf: From a8d52eb1d4343c205a55b10e85445b8a87df9072 Mon Sep 17 00:00:00 2001 From: Augustin Trancart Date: Fri, 8 May 2020 18:05:49 +0200 Subject: [PATCH 076/482] Avoid crashing when service name is not provided as log source --- src/yunohost/service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index a048c5a41..40a0fcc0b 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -70,7 +70,8 @@ def service_add(name, description=None, log=None, log_type=None, test_status=Non if log_type is not None: logger.warning("/!\\ Packagers! --log_type is deprecated. You do not need to specify --log_type systemd anymore ... Yunohost now automatically fetch the journalctl of the systemd service by default.") # Usually when adding such a service, the service name will be provided so we remove it as it's not a log file path - log.remove(name) + if name in log: + log.remove(name) service['log'] = log From 03de14df5323c47f0834deb6e0c7470ffbf4244e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 May 2020 21:45:11 +0200 Subject: [PATCH 077/482] Tweak test if domain is ready for ACME challenge --- locales/en.json | 5 +++-- src/yunohost/certificate.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/locales/en.json b/locales/en.json index 358ed64c3..dea03fe53 100644 --- a/locales/en.json +++ b/locales/en.json @@ -122,9 +122,10 @@ "certmanager_cert_signing_failed": "Could not sign the new certificate", "certmanager_certificate_fetching_or_enabling_failed": "Trying to use the new certificate for {domain:s} did not work…", "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_not_diagnosed_yet": "There is no diagnosis result for domain %s yet. Please re-run a diagnosis for categories 'DNS records' and 'Web' in the diagnosis section to check if the domain is ready for Let's Encrypt. (Or if you know what you are doing, use '--no-checks' to turn off those checks.)", "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' to do so.)", - "certmanager_domain_dns_ip_differs_from_public_ip": "The DNS 'A' record for the domain '{domain:s}' is different from this server's IP. If you recently modified your A record, please wait for it to propagate (some DNS propagation checkers are available online). (If you know what you are doing, use '--no-checks' to turn off those checks.)", - "certmanager_domain_http_not_working": "It seems the domain {domain:s} cannot be accessed through HTTP. Check that your DNS and NGINX configuration is correct", + "certmanager_domain_dns_ip_differs_from_public_ip": "The DNS records for domain '{domain:s}' is different from this server's IP. Please check the 'DNS records' (basic) category in the diagnosis for more info. If you recently modified your A record, please wait for it to propagate (some DNS propagation checkers are available online). (If you know what you are doing, use '--no-checks' to turn off those checks.)", + "certmanager_domain_http_not_working": "Domain {domain:s} does not seem to be accessible through HTTP. Please check the 'Web' category in the diagnosis for more info. (If you know what you are doing, use '--no-checks' to turn off those checks.)", "certmanager_domain_unknown": "Unknown domain '{domain:s}'", "certmanager_error_no_A_record": "No DNS 'A' record found for '{domain:s}'. You need to make your domain name point to your machine to be able to install a Let's Encrypt certificate. (If you know what you are doing, use '--no-checks' to turn off those checks.)", "certmanager_warning_subdomain_dns_record": "Subdomain '{subdomain:s}' does not resolve to the same IP address as '{domain:s}'. Some features will not be available until you fix this and regenerate the certificate.", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index f3971be06..c1f18714c 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -40,8 +40,9 @@ from moulinette.utils.filesystem import read_file from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate from yunohost.utils.error import YunohostError -from yunohost.utils.network import get_public_ip +from yunohost.utils.network import get_public_ip, dig +from yunohost.diagnosis import Diagnoser from yunohost.service import _run_service_command from yunohost.regenconf import regen_conf from yunohost.log import OperationLogger @@ -790,14 +791,19 @@ def _backup_current_cert(domain): def _check_domain_is_ready_for_ACME(domain): - public_ip = get_public_ip() + + dnsrecords = Diagnoser.get_cached_report("dnsrecords", item={"domain": domain, "category": "basic"}) or {} + httpreachable = Diagnoser.get_cached_report("web", item={"domain": domain}) or {} + + if not dnsrecords or not httpreachable: + raise YunohostError('certmanager_domain_not_diagnosed_yet', domain=domain) # Check if IP from DNS matches public IP - if not _dns_ip_match_public_ip(public_ip, domain): + if not dnsrecords.get("status") in ["SUCCESS", "WARNING"]: # Warning is for missing IPv6 record which ain't critical for ACME raise YunohostError('certmanager_domain_dns_ip_differs_from_public_ip', domain=domain) # Check if domain seems to be accessible through HTTP? - if not _domain_is_accessible_through_HTTP(public_ip, domain): + if not httpreachable.get("status") == "SUCCESS": raise YunohostError('certmanager_domain_http_not_working', domain=domain) From 333347dbcd498900dd8d760e61409d8fe3ccf4c6 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 May 2020 21:48:36 +0200 Subject: [PATCH 078/482] Clarify the steps : first validate, then start logger, then run the actual install/renew --- src/yunohost/certificate.py | 71 ++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index c1f18714c..cf11d9639 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -273,30 +273,36 @@ def _certificate_install_letsencrypt(domain_list, force=False, no_checks=False, # Actual install steps for domain in domain_list: - operation_logger = OperationLogger('letsencrypt_cert_install', [('domain', domain)], - args={'force': force, 'no_checks': no_checks, - 'staging': staging}) + if not no_checks: + try: + _check_domain_is_ready_for_ACME(domain) + except Exception as e: + logger.error(e) + continue + logger.info( "Now attempting install of certificate for domain %s!", domain) + operation_logger = OperationLogger('letsencrypt_cert_install', [('domain', domain)], + args={'force': force, 'no_checks': no_checks, + 'staging': staging}) + operation_logger.start() + try: - if not no_checks: - _check_domain_is_ready_for_ACME(domain) - - operation_logger.start() - _fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks) + except Exception as e: + msg = "Certificate installation for %s failed !\nException: %s" % (domain, e) + logger.error(msg) + operation_logger.error(msg) + if no_checks: + logger.error("Please consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a Let's Encrypt certificate on domain %s." % domain) + else: _install_cron(no_checks=no_checks) logger.success( m18n.n("certmanager_cert_install_success", domain=domain)) operation_logger.success() - except Exception as e: - _display_debug_information(domain) - msg = "Certificate installation for %s failed !\nException: %s" % (domain, e) - logger.error(msg) - operation_logger.error(msg) def certificate_renew(domain_list, force=False, no_checks=False, email=False, staging=False): @@ -367,32 +373,35 @@ def certificate_renew(domain_list, force=False, no_checks=False, email=False, st # Actual renew steps for domain in domain_list: - operation_logger = OperationLogger('letsencrypt_cert_renew', [('domain', domain)], - args={'force': force, 'no_checks': no_checks, - 'staging': staging, 'email': email}) + if not no_checks: + try: + _check_domain_is_ready_for_ACME(domain) + except: + msg = "Certificate renewing for %s failed !" % (domain) + logger.error(msg) + if email: + logger.error("Sending email with details to root ...") + _email_renewing_failed(domain, msg) + continue logger.info( "Now attempting renewing of certificate for domain %s !", domain) + operation_logger = OperationLogger('letsencrypt_cert_renew', [('domain', domain)], + args={'force': force, 'no_checks': no_checks, + 'staging': staging, 'email': email}) + operation_logger.start() + try: - if not no_checks: - _check_domain_is_ready_for_ACME(domain) - - operation_logger.start() - _fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks) - - logger.success( - m18n.n("certmanager_cert_renew_success", domain=domain)) - - operation_logger.success() - except Exception as e: import traceback from StringIO import StringIO stack = StringIO() traceback.print_exc(file=stack) msg = "Certificate renewing for %s failed !" % (domain) + if no_checks: + msg += "\nPlease consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a Let's Encrypt certificate on domain %s." % domain logger.error(msg) operation_logger.error(msg) logger.error(stack.getvalue()) @@ -400,7 +409,11 @@ def certificate_renew(domain_list, force=False, no_checks=False, email=False, st if email: logger.error("Sending email with details to root ...") - _email_renewing_failed(domain, e, stack.getvalue()) + _email_renewing_failed(domain, msg + "\n" + e, stack.getvalue()) + else: + logger.success( + m18n.n("certmanager_cert_renew_success", domain=domain)) + operation_logger.success() # # Back-end stuff # @@ -432,7 +445,7 @@ def _install_cron(no_checks=False): _set_permissions(cron_job_file, "root", "root", 0o755) -def _email_renewing_failed(domain, exception_message, stack): +def _email_renewing_failed(domain, exception_message, stack=""): from_ = "certmanager@%s (Certificate Manager)" % domain to_ = "root" subject_ = "Certificate renewing attempt for %s failed!" % domain From 713d4926c938a183c0094319b274a541065b2dcb Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 May 2020 21:50:23 +0200 Subject: [PATCH 079/482] Fix the way we check the A record for xmpp --- src/yunohost/certificate.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index cf11d9639..11d066ff2 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -610,10 +610,9 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # For "parent" domains, include xmpp-upload subdomain in subject alternate names if domain in domain_list(exclude_subdomains=True)["domains"]: subdomain = "xmpp-upload." + domain - try: - _dns_ip_match_public_ip(get_public_ip(), subdomain) + if dig(subdomain, "A", resolvers="force_external") == ("ok", [get_public_ip()]): csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) - except YunohostError: + else: logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) # Set the key From 33caf9cf330563a93036e34debf321f6c50f6c85 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 May 2020 21:50:41 +0200 Subject: [PATCH 080/482] Cleanup, we don't really need this anymore --- locales/en.json | 2 -- src/yunohost/certificate.py | 67 ------------------------------------- 2 files changed, 69 deletions(-) diff --git a/locales/en.json b/locales/en.json index dea03fe53..ed46b1b6b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -127,10 +127,8 @@ "certmanager_domain_dns_ip_differs_from_public_ip": "The DNS records for domain '{domain:s}' is different from this server's IP. Please check the 'DNS records' (basic) category in the diagnosis for more info. If you recently modified your A record, please wait for it to propagate (some DNS propagation checkers are available online). (If you know what you are doing, use '--no-checks' to turn off those checks.)", "certmanager_domain_http_not_working": "Domain {domain:s} does not seem to be accessible through HTTP. Please check the 'Web' category in the diagnosis for more info. (If you know what you are doing, use '--no-checks' to turn off those checks.)", "certmanager_domain_unknown": "Unknown domain '{domain:s}'", - "certmanager_error_no_A_record": "No DNS 'A' record found for '{domain:s}'. You need to make your domain name point to your machine to be able to install a Let's Encrypt certificate. (If you know what you are doing, use '--no-checks' to turn off those checks.)", "certmanager_warning_subdomain_dns_record": "Subdomain '{subdomain:s}' does not resolve to the same IP address as '{domain:s}'. Some features will not be available until you fix this and regenerate the certificate.", "certmanager_hit_rate_limit": "Too many certificates already issued for this exact set of domains {domain:s} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details", - "certmanager_http_check_timeout": "Timed out when server tried to contact itself through HTTP using a public IP address (domain '{domain:s}' with IP '{ip:s}'). You may be experiencing a hairpinning issue, or the firewall/router ahead of your server is misconfigured.", "certmanager_no_cert_file": "Could not read the certificate file for the domain {domain:s} (file: {file:s})", "certmanager_self_ca_conf_file_not_found": "Could not find configuration file for self-signing authority (file: {file:s})", "certmanager_unable_to_parse_self_CA_name": "Could not parse name of self-signing authority (file: {file:s})", diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 11d066ff2..35d019ec8 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -29,7 +29,6 @@ import pwd import grp import smtplib import subprocess -import dns.resolver import glob from datetime import datetime @@ -69,18 +68,6 @@ PRODUCTION_CERTIFICATION_AUTHORITY = "https://acme-v02.api.letsencrypt.org" INTERMEDIATE_CERTIFICATE_URL = "https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem" -DNS_RESOLVERS = [ - # FFDN DNS resolvers - # See https://www.ffdn.org/wiki/doku.php?id=formations:dns - "80.67.169.12", # FDN - "80.67.169.40", # - "89.234.141.66", # ARN - "141.255.128.100", # Aquilenet - "141.255.128.101", - "89.234.186.18", # Grifon - "80.67.188.188" # LDN -] - # # Front-end stuff # # @@ -540,7 +527,6 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False): raise YunohostError('certmanager_hit_rate_limit', domain=domain) else: logger.error(str(e)) - _display_debug_information(domain) raise YunohostError('certmanager_cert_signing_failed') except Exception as e: @@ -819,59 +805,6 @@ def _check_domain_is_ready_for_ACME(domain): raise YunohostError('certmanager_domain_http_not_working', domain=domain) -def _get_dns_ip(domain): - try: - resolver = dns.resolver.Resolver() - resolver.nameservers = DNS_RESOLVERS - answers = resolver.query(domain, "A") - except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): - raise YunohostError('certmanager_error_no_A_record', domain=domain) - - return str(answers[0]) - - -def _dns_ip_match_public_ip(public_ip, domain): - return _get_dns_ip(domain) == public_ip - - -def _domain_is_accessible_through_HTTP(ip, domain): - import requests # lazy loading this module for performance reasons - try: - requests.head("http://" + ip, headers={"Host": domain}, timeout=10) - except requests.exceptions.Timeout as e: - logger.warning(m18n.n('certmanager_http_check_timeout', domain=domain, ip=ip)) - return False - except Exception as e: - logger.debug("Couldn't reach domain '%s' by requesting this ip '%s' because: %s" % (domain, ip, e)) - return False - - return True - - -def _get_local_dns_ip(domain): - try: - resolver = dns.resolver.Resolver() - answers = resolver.query(domain, "A") - except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): - logger.warning("Failed to resolved domain '%s' locally", domain) - return None - - return str(answers[0]) - - -def _display_debug_information(domain): - dns_ip = _get_dns_ip(domain) - public_ip = get_public_ip() - local_dns_ip = _get_local_dns_ip(domain) - - logger.warning("""\ -Debug information: - - domain ip from DNS %s - - domain ip from local DNS %s - - public ip of the server %s -""", dns_ip, local_dns_ip, public_ip) - - # FIXME / TODO : ideally this should not be needed. There should be a proper # mechanism to regularly check the value of the public IP and trigger # corresponding hooks (e.g. dyndns update and dnsmasq regen-conf) From a799740afa7feeb37f6fe25d9f49434ad93f5794 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 May 2020 23:47:18 +0200 Subject: [PATCH 081/482] Move meltdown check to base system --- data/hooks/diagnosis/00-basesystem.py | 74 +++++++++++++++++++- data/hooks/diagnosis/90-security.py | 98 --------------------------- locales/en.json | 2 - 3 files changed, 73 insertions(+), 101 deletions(-) delete mode 100644 data/hooks/diagnosis/90-security.py diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index 51926924a..dbb0ccf08 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -1,9 +1,11 @@ #!/usr/bin/env python import os +import json +import subprocess from moulinette.utils.process import check_output -from moulinette.utils.filesystem import read_file +from moulinette.utils.filesystem import read_file, read_json, write_to_json from yunohost.diagnosis import Diagnoser from yunohost.utils.packages import ynh_packages_version @@ -74,5 +76,75 @@ class BaseSystemDiagnoser(Diagnoser): details=ynh_version_details) + if self.is_vulnerable_to_meltdown(): + yield dict(meta={"test": "meltdown"}, + status="ERROR", + summary="diagnosis_security_vulnerable_to_meltdown", + details=["diagnosis_security_vulnerable_to_meltdown_details"] + ) + + def is_vulnerable_to_meltdown(self): + # 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" + if os.path.exists(cache_file): + if not os.path.exists(dpkg_log) or os.path.getmtime(cache_file) > os.path.getmtime(dpkg_log): + self.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 + SCRIPT_PATH = "/usr/lib/moulinette/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh" + + # '--variant 3' corresponds to Meltdown + # example output from the script: + # [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}] + try: + self.logger_debug("Running meltdown vulnerability checker") + call = subprocess.Popen("bash %s --batch json --variant 3" % + SCRIPT_PATH, shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + # 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 + + # 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: + self.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" + except Exception as e: + import traceback + traceback.print_exc() + self.logger_warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e) + raise Exception("Command output for failed meltdown check: '%s'" % output) + + self.logger_debug("Writing results from meltdown checker to cache file, %s" % cache_file) + write_to_json(cache_file, CVEs) + return CVEs[0]["VULNERABLE"] + + def main(args, env, loggers): return BaseSystemDiagnoser(args, env, loggers).diagnose() diff --git a/data/hooks/diagnosis/90-security.py b/data/hooks/diagnosis/90-security.py deleted file mode 100644 index d281042b0..000000000 --- a/data/hooks/diagnosis/90-security.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python - -import os -import json -import subprocess - -from yunohost.diagnosis import Diagnoser -from moulinette.utils.filesystem import read_json, write_to_json - - -class SecurityDiagnoser(Diagnoser): - - id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] - cache_duration = 3600 - dependencies = [] - - def run(self): - - "CVE-2017-5754" - - if self.is_vulnerable_to_meltdown(): - yield dict(meta={"test": "meltdown"}, - status="ERROR", - summary="diagnosis_security_vulnerable_to_meltdown", - details=["diagnosis_security_vulnerable_to_meltdown_details"] - ) - else: - yield dict(meta={}, - status="SUCCESS", - summary="diagnosis_security_all_good" - ) - - - def is_vulnerable_to_meltdown(self): - # 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" - if os.path.exists(cache_file): - if not os.path.exists(dpkg_log) or os.path.getmtime(cache_file) > os.path.getmtime(dpkg_log): - self.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 - SCRIPT_PATH = "/usr/lib/moulinette/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh" - - # '--variant 3' corresponds to Meltdown - # example output from the script: - # [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}] - try: - self.logger_debug("Running meltdown vulnerability checker") - call = subprocess.Popen("bash %s --batch json --variant 3" % - SCRIPT_PATH, shell=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - # 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 - - # 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: - self.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" - except Exception as e: - import traceback - traceback.print_exc() - self.logger_warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e) - raise Exception("Command output for failed meltdown check: '%s'" % output) - - self.logger_debug("Writing results from meltdown checker to cache file, %s" % cache_file) - write_to_json(cache_file, CVEs) - return CVEs[0]["VULNERABLE"] - - -def main(args, env, loggers): - return SecurityDiagnoser(args, env, loggers).diagnose() diff --git a/locales/en.json b/locales/en.json index 358ed64c3..1be70d24b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -224,7 +224,6 @@ "diagnosis_regenconf_allgood": "All configurations files are in line with the recommended configuration!", "diagnosis_regenconf_manually_modified": "Configuration file {file} appears to have been manually modified.", "diagnosis_regenconf_manually_modified_details": "This is probably OK if you know what you're doing! YunoHost will stop updating this file automatically... But beware that YunoHost upgrades could contain important recommended changes. If you want to, you can inspect the differences with yunohost tools regen-conf {category} --dry-run --with-diff and force the reset to the recommended configuration with yunohost tools regen-conf {category} --force", - "diagnosis_security_all_good": "No critical security vulnerability was found.", "diagnosis_security_vulnerable_to_meltdown": "You appear vulnerable to the Meltdown criticial security vulnerability", "diagnosis_security_vulnerable_to_meltdown_details": "To fix this, you should upgrade your system and reboot to load the new linux kernel (or contact your server provider if this doesn't work). See https://meltdownattack.com/ for more infos.", "diagnosis_description_basesystem": "Base system", @@ -236,7 +235,6 @@ "diagnosis_description_web": "Web", "diagnosis_description_mail": "Email", "diagnosis_description_regenconf": "System configurations", - "diagnosis_description_security": "Security checks", "diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside in IPv{ipversion}.", "diagnosis_ports_could_not_diagnose_details": "Error: {error}", "diagnosis_ports_unreachable": "Port {port} is not reachable from outside.", From 23147161d68f3e8214c28759702c26b08cb446d1 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 8 May 2020 23:56:23 +0200 Subject: [PATCH 082/482] Change warning/errors about swap as info instead ... add a tip about the fact that having swap on SD or SSD is dangerous --- data/hooks/diagnosis/50-systemresources.py | 5 +++-- locales/en.json | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index 66d27866a..50f69f9ed 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -45,14 +45,15 @@ class SystemResourcesDiagnoser(Diagnoser): item = dict(meta={"test": "swap"}, data={"total": human_size(swap.total), "recommended": "512 MiB"}) if swap.total <= 1 * MB: - item["status"] = "ERROR" + item["status"] = "INFO" item["summary"] = "diagnosis_swap_none" elif swap.total < 500 * MB: - item["status"] = "WARNING" + item["status"] = "INFO" item["summary"] = "diagnosis_swap_notsomuch" else: item["status"] = "SUCCESS" item["summary"] = "diagnosis_swap_ok" + item["details"] = ["diagnosis_swap_tip"] yield item # FIXME : add a check that swapiness is low if swap is on a sdcard... diff --git a/locales/en.json b/locales/en.json index 1be70d24b..6f4fcac1d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -193,6 +193,7 @@ "diagnosis_swap_none": "The system has no swap at all. You should consider adding at least {recommended} of swap to avoid situations where the system runs out of memory.", "diagnosis_swap_notsomuch": "The system has only {total} swap. You should consider having at least {recommended} to avoid situations where the system runs out of memory.", "diagnosis_swap_ok": "The system has {total} of swap!", + "diagnosis_swap_tip": "Please be careful and aware that if the server is hosting swap on an SD card or SSD storage, it may drastically reduce the life expectancy of the device`.", "diagnosis_mail_outgoing_port_25_ok": "The SMTP mail server is able to send emails (outgoing port 25 is not blocked).", "diagnosis_mail_outgoing_port_25_blocked": "The SMTP mail server cannot send emails to other servers because outgoing port 25 is blocked in IPv{ipversion}.", "diagnosis_mail_outgoing_port_25_blocked_details": "You should first try to unblock outgoing port 25 in your internet router interface or your hosting provider interface. (Some hosting provider may require you to send them a support ticket for this).", From aecbb14aa4ccec48ccb2fe62ca1aa9337e85a618 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 May 2020 01:46:28 +0200 Subject: [PATCH 083/482] Add a --human-readable option to diagnosis_show() and a --email to diagnosis_run() to email issues found by cron job --- data/actionsmap/yunohost.yml | 6 +++ data/hooks/conf_regen/01-yunohost | 2 +- src/yunohost/diagnosis.py | 61 +++++++++++++++++++++++++------ 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index e2b4447cf..d61538c5c 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1691,6 +1691,9 @@ diagnosis: --share: help: Share the logs using yunopaste action: store_true + --human-readable: + help: Show a human-readable output + action: store_true run: action_help: Run diagnosis @@ -1705,6 +1708,9 @@ diagnosis: --except-if-never-ran-yet: help: Don't run anything if diagnosis never ran yet ... (this is meant to be used by the webadmin) action: store_true + --email: + help: Send an email to root with issues found (this is meant to be used by cron job) + action: store_true ignore: action_help: Configure some diagnosis results to be ignored and therefore not considered as actual issues diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index b24689023..4bd763b70 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -60,7 +60,7 @@ do_pre_regen() { mkdir -p $pending_dir/etc/cron.d/ cat > $pending_dir/etc/cron.d/yunohost-diagnosis << EOF SHELL=/bin/bash -0 7,19 * * * root : YunoHost Diagnosis; sleep \$((RANDOM\\%600)); yunohost diagnosis run > /dev/null +0 7,19 * * * root : YunoHost Automatic Diagnosis; sleep \$((RANDOM\\%600)); yunohost diagnosis run --email > /dev/null 2>/dev/null || echo "Running the automatic diagnosis failed miserably" EOF } diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index bfb2619eb..806285f52 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -27,6 +27,7 @@ import re import os import time +import smtplib from moulinette import m18n, msettings from moulinette.utils import log @@ -41,6 +42,7 @@ DIAGNOSIS_CACHE = "/var/cache/yunohost/diagnosis/" DIAGNOSIS_CONFIG_FILE = '/etc/yunohost/diagnosis.yml' DIAGNOSIS_SERVER = "diagnosis.yunohost.org" + def diagnosis_list(): all_categories_names = [h for h, _ in _list_diagnosis_categories()] return {"categories": all_categories_names} @@ -65,7 +67,7 @@ def diagnosis_get(category, item): return Diagnoser.get_cached_report(category, item=item) -def diagnosis_show(categories=[], issues=False, full=False, share=False): +def diagnosis_show(categories=[], issues=False, full=False, share=False, human_readable=False): if not os.path.exists(DIAGNOSIS_CACHE): logger.warning(m18n.n("diagnosis_never_ran_yet")) @@ -93,7 +95,7 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): logger.error(m18n.n("diagnosis_failed", category=category, error=str(e))) continue - Diagnoser.i18n(report) + Diagnoser.i18n(report, force_remove_html_tags=share or human_readable) add_ignore_flag_to_issues(report) if not full: @@ -123,9 +125,12 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False): return {"url": url} else: return + elif human_readable: + print(_dump_human_readable_reports(all_reports)) else: return {"reports": all_reports} + def _dump_human_readable_reports(reports): output = "" @@ -137,16 +142,16 @@ def _dump_human_readable_reports(reports): for item in report["items"]: output += "[{status}] {summary}\n".format(**item) for detail in item.get("details", []): - output += " - " + detail + "\n" + output += " - " + detail.replace("\n", "\n ") + "\n" output += "\n" output += "\n\n" return(output) -def diagnosis_run(categories=[], force=False, except_if_never_ran_yet=False): +def diagnosis_run(categories=[], force=False, except_if_never_ran_yet=False, email=False): - if except_if_never_ran_yet and not os.path.exists(DIAGNOSIS_CACHE): + if (email or except_if_never_ran_yet) and not os.path.exists(DIAGNOSIS_CACHE): return # Get all the categories @@ -170,7 +175,7 @@ def diagnosis_run(categories=[], force=False, except_if_never_ran_yet=False): try: code, report = hook_exec(path, args={"force": force}, env=None) - except Exception as e: + except Exception: import traceback logger.error(m18n.n("diagnosis_failed_for_category", category=category, error='\n'+traceback.format_exc())) else: @@ -178,10 +183,11 @@ def diagnosis_run(categories=[], force=False, except_if_never_ran_yet=False): if report != {}: issues.extend([item for item in report["items"] if item["status"] in ["WARNING", "ERROR"]]) - if issues and msettings.get("interface") == "cli": - logger.warning(m18n.n("diagnosis_display_tip")) - - return + if issues: + if email: + _email_diagnosis_issues() + elif msettings.get("interface") == "cli": + logger.warning(m18n.n("diagnosis_display_tip")) def diagnosis_ignore(add_filter=None, remove_filter=None, list=False): @@ -318,6 +324,7 @@ def issue_matches_criterias(issue, criterias): return False return True + def add_ignore_flag_to_issues(report): """ Iterate over issues in a report, and flag them as ignored if they match an @@ -448,7 +455,7 @@ class Diagnoser(): return descr if descr != key else id_ @staticmethod - def i18n(report): + def i18n(report, force_remove_html_tags=False): # "Render" the strings with m18n.n # N.B. : we do those m18n.n right now instead of saving the already-translated report @@ -477,7 +484,7 @@ class Diagnoser(): info[1].update(meta_data) s = m18n.n(info[0], **(info[1])) # In cli, we remove the html tags - if msettings.get("interface") != "api": + if msettings.get("interface") != "api" or force_remove_html_tags: s = s.replace("", "'").replace("", "'") s = html_tags.sub('', s.replace("
","\n")) else: @@ -547,3 +554,33 @@ def _list_diagnosis_categories(): hooks.append((name, info["path"])) return hooks + + +def _email_diagnosis_issues(): + from yunohost.domain import _get_maindomain + from_ = "diagnosis@%s (Automatic diagnosis)" % _get_maindomain() + to_ = "root" + subject_ = "Issues found by automatic diagnosis" + + disclaimer = "The automatic diagnosis on your YunoHost server identified some issues on your server. You will find a description of the issues below. You can manage those issues in the 'Diagnosis' section in your webadmin." + + content = _dump_human_readable_reports(diagnosis_show(issues=True)["reports"]) + + message = """\ +From: %s +To: %s +Subject: %s + +%s + +--- + +%s +""" % (from_, to_, subject_, disclaimer, content) + + print(message) + + smtp = smtplib.SMTP("localhost") + smtp.sendmail(from_, [to_], message) + smtp.quit() + From d8dfa1c5d5f6626954c13a96bd930f3ce710f5a0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 May 2020 15:58:40 +0200 Subject: [PATCH 084/482] We gotta trash the error stream because gzip complains about broken pipe when ran in python subprocess ~.~ --- src/yunohost/utils/packages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/utils/packages.py b/src/yunohost/utils/packages.py index 6103206e5..51e9ab71a 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -40,7 +40,7 @@ def get_ynh_package_version(package): # may handle changelog differently ! changelog = "/usr/share/doc/%s/changelog.gz" % package - cmd = "gzip -cd %s | head -n1" % changelog + cmd = "gzip -cd %s 2>/dev/null | head -n1" % changelog if not os.path.exists(changelog): return {"version": "?", "repo": "?"} out = check_output(cmd).split() From c8625858e2940212072a7c646430c703adf78027 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 May 2020 18:01:16 +0200 Subject: [PATCH 085/482] Fetch xmpp-upload DNS record status from diagnosis directly --- src/yunohost/certificate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 35d019ec8..366f45462 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -39,7 +39,7 @@ from moulinette.utils.filesystem import read_file from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate from yunohost.utils.error import YunohostError -from yunohost.utils.network import get_public_ip, dig +from yunohost.utils.network import get_public_ip from yunohost.diagnosis import Diagnoser from yunohost.service import _run_service_command @@ -596,7 +596,8 @@ def _prepare_certificate_signing_request(domain, key_file, output_folder): # For "parent" domains, include xmpp-upload subdomain in subject alternate names if domain in domain_list(exclude_subdomains=True)["domains"]: subdomain = "xmpp-upload." + domain - if dig(subdomain, "A", resolvers="force_external") == ("ok", [get_public_ip()]): + xmpp_records = Diagnoser.get_cached_report("dnsrecords", item={"domain": domain, "category": "xmpp"}).get("data") or {} + if xmpp_records.get("CNAME:xmpp-upload") == "OK": csr.add_extensions([crypto.X509Extension("subjectAltName", False, "DNS:" + subdomain)]) else: logger.warning(m18n.n('certmanager_warning_subdomain_dns_record', subdomain=subdomain, domain=domain)) From 232c5f3d6b0f6a8a64eb541778a9862e9d766c9e Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Tue, 28 Apr 2020 22:39:03 +0000 Subject: [PATCH 086/482] Translated using Weblate (Catalan) Currently translated at 100.0% (632 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 70 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index bd071e354..e5174205d 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -504,7 +504,7 @@ "diagnosis_basesystem_ynh_main_version": "El servidor funciona amb YunoHost {main_version} ({repo})", "diagnosis_ram_low": "El sistema només té {available} ({available_percent}%) de memòria RAM disponibles d'un total de {total}. Aneu amb compte.", "diagnosis_swap_none": "El sistema no té swap. Hauríeu de considerar afegir un mínim de {recommended} de swap per evitar situacions en les que el sistema es queda sense memòria.", - "diagnosis_regenconf_manually_modified": "El fitxer de configuració {file} ha estat modificat manualment.", + "diagnosis_regenconf_manually_modified": "El fitxer de configuració {file} sembla haver estat modificat manualment.", "diagnosis_security_vulnerable_to_meltdown_details": "Per arreglar-ho, hauríeu d'actualitzar i reiniciar el sistema per tal de carregar el nou nucli de linux (o contactar amb el proveïdor del servidor si no funciona). Vegeu https://meltdownattack.com/ per a més informació.", "diagnosis_http_could_not_diagnose": "No s'ha pogut diagnosticar si el domini és accessible des de l'exterior.", "diagnosis_http_could_not_diagnose_details": "Error: {error}", @@ -531,23 +531,23 @@ "diagnosis_ip_not_connected_at_all": "Sembla que el servidor no està connectat a internet!?", "diagnosis_ip_dnsresolution_working": "La resolució de nom de domini està funcionant!", "diagnosis_ip_broken_dnsresolution": "La resolució de nom de domini falla per algun motiu… Està el tallafocs bloquejant les peticions DNS?", - "diagnosis_ip_broken_resolvconf": "La resolució de nom de domini sembla caiguda en el servidor, podria estar relacionat amb el fet que /etc/resolv.conf no apunta cap a 127.0.0.1.", - "diagnosis_ip_weird_resolvconf": "La resolució DNS sembla estar funcionant, però aneu amb compte ja que esteu utilitzant un versió personalitzada de /etc/resolv.conf.", - "diagnosis_ip_weird_resolvconf_details": "En canvi, aquest fitxer hauria de ser un enllaç simbòlic cap a /etc/resolvconf/run/resolv.conf i que aquest apunti cap a 127.0.0.1 (dnsmasq). La configuració del «resolver» real s'hauria de fer a /etc/resolv.dnsmaq.conf.", - "diagnosis_dns_good_conf": "Bona configuració DNS pel domini {domain} (categoria {category})", - "diagnosis_dns_bad_conf": "Configuració DNS incorrecta o inexistent pel domini {domain} (categoria {category})", - "diagnosis_dns_missing_record": "Segons la configuració DNS recomanada, hauríeu d'afegir un registre DNS\ntipus: {type}\nnom: {name}\nvalor: {value}.", + "diagnosis_ip_broken_resolvconf": "La resolució de nom de domini sembla caiguda en el servidor, podria estar relacionat amb el fet que /etc/resolv.conf no apunta cap a 127.0.0.1.", + "diagnosis_ip_weird_resolvconf": "La resolució DNS sembla estar funcionant, però sembla que esteu utilitzant un versió personalitzada de /etc/resolv.conf.", + "diagnosis_ip_weird_resolvconf_details": "El fitxer etc/resolv.conf hauria de ser un enllaç simbòlic cap a /etc/resolvconf/run/resolv.conf i que aquest apunti cap a 127.0.0.1 (dnsmasq). La configuració del «resolver» real s'hauria de fer a /etc/resolv.dnsmaq.conf.", + "diagnosis_dns_good_conf": "Els registres DNS han estat correctament configurats pel domini {domain} (categoria {category})", + "diagnosis_dns_bad_conf": "Alguns registres DNS són incorrectes o no existeixen pel domini {domain} (categoria {category})", + "diagnosis_dns_missing_record": "Segons la configuració DNS recomanada, hauríeu d'afegir un registre DNS amb la següent informació.
Tipus: {type}
Nom: {name}
Valor: {value}", "diagnosis_dns_discrepancy": "El registre DNS de tipus {type} i nom {name} no concorda amb la configuració recomanada.\nValor actual: {current}\nValor esperat: {value}", "diagnosis_services_bad_status": "El servei {service} està {status} :(", - "diagnosis_diskusage_verylow": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) només té disponibles {free} ({free_percent}%). Hauríeu de considerar alliberar una mica d'espai.", - "diagnosis_diskusage_low": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) només té disponibles {free} ({free_percent}%). Aneu amb compte.", - "diagnosis_diskusage_ok": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) encara té {free} ({free_percent}%) lliures!", + "diagnosis_diskusage_verylow": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) només té disponibles {free} ({free_percent}%). Hauríeu de considerar alliberar una mica d'espai!", + "diagnosis_diskusage_low": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) només té disponibles {free} ({free_percent}%). Aneu amb compte.", + "diagnosis_diskusage_ok": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) encara té {free} ({free_percent}%) lliures!", "diagnosis_ram_verylow": "El sistema només té {available} ({available_percent}%) de memòria RAM disponibles! (d'un total de {total})", "diagnosis_ram_ok": "El sistema encara té {available} ({available_percent}%) de memòria RAM disponibles d'un total de {total}.", "diagnosis_swap_notsomuch": "El sistema només té {total} de swap. Hauríeu de considerar tenir un mínim de {recommended} per evitar situacions en les que el sistema es queda sense memòria.", "diagnosis_swap_ok": "El sistema té {total} de swap!", "diagnosis_regenconf_allgood": "Tots els fitxers de configuració estan en acord amb la configuració recomanada!", - "diagnosis_regenconf_manually_modified_details": "No hauria de ser cap problema sempre i quan sapigueu el que esteu fent ;) !", + "diagnosis_regenconf_manually_modified_details": "No hauria de ser cap problema sempre i quan sapigueu el que esteu fent! YunoHost deixarà d'actualitzar aquest fitxer de manera automàtica… Però tingueu en compte que les actualitzacions de YunoHost podrien tenir canvis recomanats importants. Si voleu podeu mirar les diferències amb yunohost tools regen-conf {category} --dry-run --with-diff i forçar el restabliment de la configuració recomanada amb yunohost tools regen-conf {category} --force", "diagnosis_regenconf_manually_modified_debian": "El fitxer de configuració {file} ha estat modificat manualment respecte al fitxer per defecte de Debian.", "diagnosis_regenconf_manually_modified_debian_details": "No hauria de ser cap problema, però ho haureu de vigilar...", "diagnosis_security_all_good": "No s'ha trobat cap vulnerabilitat de seguretat crítica.", @@ -577,11 +577,11 @@ "diagnosis_description_mail": "Correu electrònic", "migration_description_0013_futureproof_apps_catalog_system": "Migrar al nou sistema de catàleg d'aplicacions resistent al pas del temps", "app_upgrade_script_failed": "Hi ha hagut un error en el script d'actualització de l'aplicació", - "diagnosis_services_bad_status_tip": "Podeu intentar reiniciar el servei, i si no funciona, podeu mirar els registres del servei utilitzant «yunohost service log {service}» o a través de «Serveis» a la secció de la pàgina web d'administració.", - "diagnosis_ports_forwarding_tip": "Per arreglar aquest problema, segurament s'ha de configurar el reenviament de ports en el router tal i com s'explica a https://yunohost.org/isp_box_config", - "diagnosis_http_bad_status_code": "El sistema de diagnòstic no ha pogut connectar amb el servidor. Podria ser que una altra màquina hagi contestat en lloc del servidor. S'hauria de comprovar que el reenviament del port 80 sigui correcte, que la configuració NGINX està actualitzada i que el reverse-proxy no està interferint.", + "diagnosis_services_bad_status_tip": "Podeu intentar reiniciar el servei, i si no funciona, podeu mirar els registres a la pàgina web d'administració (des de la línia de comandes, ho podeu fer utilitzant yunohost service restart {service} i yunohost service log {service}).", + "diagnosis_ports_forwarding_tip": "Per arreglar aquest problema, segurament s'ha de configurar el reenviament de ports en el router tal i com s'explica a https://yunohost.org/isp_box_config", + "diagnosis_http_bad_status_code": "Sembla que una altra màquina (potser el router) a respost en lloc del vostre servidor.
1. La causa més probable per a aquest problema és que el port 80 (i 443) no reenvien correctament cap al vostre servidor.
2. En configuracions més complexes: assegureu-vos que no hi ha cap tallafoc o reverse-proxy interferint.", "diagnosis_no_cache": "Encara no hi ha memòria cau pel diagnòstic de la categoria «{category}»", - "diagnosis_http_timeout": "S'ha exhaurit el temps d'esperar intentant connectar amb el servidor des de l'exterior. Sembla que no s'hi pot accedir. S'hauria de comprovar que el reenviament del port 80 és correcte, que NGINX funciona, i que el tallafocs no està interferint.", + "diagnosis_http_timeout": "S'ha exhaurit el temps d'esperar intentant connectar amb el servidor des de l'exterior.
1. La causa més probable per a aquest problema és que el port 80 (i 443) no reenvien correctament cap al vostre servidor.
2. També us hauríeu d'assegurar que el servei nginx estigui funcionant
3. En configuracions més complexes: assegureu-vos que no hi ha cap tallafoc o reverse-proxy interferint.", "diagnosis_http_connection_error": "Error de connexió: no s'ha pogut connectar amb el domini demanat, segurament és inaccessible.", "yunohost_postinstall_end_tip": "S'ha completat la post-instal·lació. Per acabar la configuració, considereu:\n - afegir un primer usuari a través de la secció «Usuaris» a la pàgina web d'administració (o emprant «yunohost user create » a la línia d'ordres);\n - diagnosticar possibles problemes a través de la secció «Diagnòstics» a la pàgina web d'administració (o emprant «yunohost diagnosis run» a la línia d'ordres);\n - llegir les seccions «Finalizing your setup» i «Getting to know Yunohost» a la documentació per administradors: https://yunohost.org/admindoc.", "migration_description_0014_remove_app_status_json": "Eliminar els fitxers d'aplicació status.json heretats", @@ -598,5 +598,43 @@ "diagnosis_basesystem_hardware": "L'arquitectura del maquinari del servidor és {virt} {arch}", "group_already_exist_on_system_but_removing_it": "El grup {group} ja existeix en els grups del sistema, però YunoHost l'eliminarà…", "certmanager_warning_subdomain_dns_record": "El subdomini «{subdomain:s}» no resol a la mateixa adreça IP que «{domain:s}». Algunes funcions no estaran disponibles fins que no s'hagi arreglat i s'hagi regenerat el certificat.", - "domain_cannot_add_xmpp_upload": "No podeu afegir dominis començant per «xmpp-upload.». Aquest tipus de nom està reservat per a la funció de pujada de XMPP integrada a YunoHost." + "domain_cannot_add_xmpp_upload": "No podeu afegir dominis començant per «xmpp-upload.». Aquest tipus de nom està reservat per a la funció de pujada de XMPP integrada a YunoHost.", + "diagnosis_display_tip": "Per veure els problemes que s'han trobat, podeu anar a la secció de Diagnòstic a la pàgina web d'administració, o utilitzar « yunohost diagnostic show --issues » a la línia de comandes.", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Alguns proveïdors no permeten desbloquejar el port de sortida 25 perquè no els hi importa la Neutralitat de la Xarxa.
- Alguns d'ells ofereixen l'alternativa d'utilitzar un relay de servidor de correu electrònic tot i que implica que el relay serà capaç d'espiar el tràfic de correus electrònics.
- Una alternativa respectuosa amb la privacitat és utilitzar una VPN *amb una IP pública dedicada* per sortejar aquest tipus de limitació. Vegeu https://yunohost.org/#/vpn_advantage
- També podeu considerar canviar-vos a un proveïdor més respectuós de la neutralitat de la xarxa", + "diagnosis_ip_global": "IP global: {global}", + "diagnosis_ip_local": "IP local: {local}", + "diagnosis_dns_point_to_doc": "Consulteu la documentació a https://yunohost.org/dns_config si necessiteu ajuda per configurar els registres DNS.", + "diagnosis_mail_outgoing_port_25_ok": "El servidor de correu electrònic SMTP pot enviar correus electrònics (el port de sortida 25 no està bloquejat).", + "diagnosis_mail_outgoing_port_25_blocked_details": "Primer heu d'intentar desbloquejar el port 25 en la interfície del vostre router o en la interfície del vostre allotjador. (Alguns proveïdors d'allotjament demanen enviar un tiquet de suport en aquests casos).", + "diagnosis_mail_ehlo_ok": "El servidor de correu electrònic SMTP no és accessible des de l'exterior i per tant no pot rebre correus electrònics!", + "diagnosis_mail_ehlo_unreachable": "El servidor de correu electrònic SMTP no és accessible des de l'exterior amb IPv{ipversion}. No podrà rebre correus electrònics.", + "diagnosis_mail_ehlo_bad_answer": "Un servei no SMTP a respost en el port 25 amb IPv{ipversion}", + "diagnosis_mail_ehlo_bad_answer_details": "Podria ser que sigui per culpa d'una altra màquina responent en lloc del servidor.", + "diagnosis_mail_ehlo_wrong": "Un servidor de correu electrònic SMTP diferent respon amb IPv{ipversion}. És probable que el vostre servidor no pugui rebre correus electrònics.", + "diagnosis_mail_ehlo_could_not_diagnose": "No s'ha pogut diagnosticar si el servidor de correu electrònic postfix és accessible des de l'exterior amb IPv{ipversion}.", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}", + "diagnosis_mail_fcrdns_ok": "S'ha configurat correctament el servidor DNS invers!", + "diagnosis_mail_blacklist_ok": "Sembla que les IPs i el dominis d'aquest servidor no són en una llista negra", + "diagnosis_mail_blacklist_listed_by": "La vostra IP o domini {item} està en una llista negra a {blacklist_name}", + "diagnosis_mail_blacklist_reason": "El motiu de ser a la llista negra és: {reason}", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "El DNS invers no està correctament configurat amb IPv{ipversion}. Alguns correus electrònics poden no arribar al destinatari o ser marcats com correu brossa.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "DNS invers actual: {rdns_domain}
Valor esperat: {ehlo_domain}", + "diagnosis_mail_queue_ok": "{nb_pending} correus electrònics pendents en les cues de correu electrònic", + "diagnosis_mail_queue_unavailable": "No s'ha pogut consultar el nombre de correus electrònics pendents en la cua", + "diagnosis_mail_queue_unavailable_details": "Error: {error}", + "diagnosis_mail_queue_too_big": "Hi ha massa correus electrònics pendents en la cua ({nb_pending} correus electrònics)", + "diagnosis_http_hairpinning_issue": "Sembla que la vostra xarxa no té el hairpinning activat.", + "diagnosis_http_nginx_conf_not_up_to_date": "La configuració NGINX d'aquest domini sembla que ha estat modificada manualment, i no deixa que YunoHost diagnostiqui si és accessible amb HTTP.", + "diagnosis_http_nginx_conf_not_up_to_date_details": "Per arreglar el problema, mireu les diferències amb la línia d'ordres utilitzant yunohost tools regen-conf nginx --dry-run --with-diff i si els canvis us semblen bé els podeu fer efectius utilitzant yunohost tools regen-conf nginx --force.", + "global_settings_setting_smtp_allow_ipv6": "Permet l'ús de IPv6 per rebre i enviar correus electrònics", + "diagnosis_mail_ehlo_unreachable_details": "No s'ha pogut establir una connexió amb el vostre servidor en el port 25 amb IPv{ipversion}. Sembla que el servidor no és accessible.
1. La causa més comú per aquest problema és que el port 25 no està correctament redireccionat cap al vostre servidor.
2. També us hauríeu d'assegurar que el servei postfix estigui funcionant.
3. En configuracions més complexes: assegureu-vos que que no hi hagi cap tallafoc ni reverse-proxy interferint.", + "diagnosis_mail_ehlo_wrong_details": "El EHLO rebut pel servidor de diagnòstic remot amb IPv{ipversion} és diferent al domini del vostre servidor.
EHLO rebut: {wrong_ehlo}
Esperat: {right_ehlo}
La causa més habitual d'aquest problema és que el port 25 no està correctament reenviat cap al vostre servidor. També podeu comprovar que no hi hagi un tallafocs o un reverse-proxy interferint.", + "diagnosis_mail_fcrdns_dns_missing": "No hi ha cap DNS invers definit per IPv{ipversion}. Alguns correus electrònics poden no entregar-se o poden ser marcats com a correu brossa.", + "diagnosis_mail_blacklist_website": "Després d'haver identificat perquè estàveu llistats i haver arreglat el problema, no dubteu en demanar que la vostra IP o domini sigui eliminat de {blacklist_website}", + "diagnosis_ports_partially_unreachable": "El port {port} no és accessible des de l'exterior amb IPv{failed}.", + "diagnosis_http_partially_unreachable": "El domini {domain} sembla que no és accessible utilitzant HTTP des de l'exterior de la xarxa local amb IPv{failed}, tot i que funciona amb IPv{passed}.", + "diagnosis_mail_fcrdns_nok_details": "Hauríeu d'intentar configurar primer el DNS invers amb {ehlo_domain} en la interfície del router o en la interfície del vostre allotjador. (Alguns allotjadors requereixen que obris un informe de suport per això).", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Alguns proveïdors no permeten configurar el DNS invers (o aquesta funció pot no funcionar…). Si teniu problemes a causa d'això, considereu les solucions següents:
- Alguns proveïdors d'accés a internet (ISP) donen l'alternativa de utilitzar un relay de servidor de correu electrònic tot i que implica que el relay podrà espiar el trànsit de correus electrònics.
- Una alternativa respectuosa amb la privacitat és utilitzar una VPN *amb una IP pública dedicada* per sobrepassar aquest tipus de limitacions. Mireu https://yunohost.org/#/vpn_advantage
- Finalment, també es pot canviar de proveïdor", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Alguns proveïdors no permeten configurar el vostre DNS invers (o la funció no els hi funciona…). Si el vostre DNS invers està correctament configurat per IPv4, podeu intentar deshabilitar l'ús de IPv6 per a enviar correus electrònics utilitzant yunohost settings set smtp.allow_ipv6 -v off. Nota: aquesta última solució implica que no podreu enviar o rebre correus electrònics cap a els pocs servidors que hi ha que només tenen IPv-6.", + "diagnosis_http_hairpinning_issue_details": "Això és probablement a causa del router del vostre proveïdor d'accés a internet. El que fa, que gent de fora de la xarxa local pugui accedir al servidor sense problemes, però no la gent de dins la xarxa local (com vostè probablement) quan s'utilitza el nom de domini o la IP global. Podreu segurament millorar la situació fent una ullada a https://yunohost.org/dns_local_network" } From ff0dca4773e44dcdc466dfd700baa06d4d47c0c9 Mon Sep 17 00:00:00 2001 From: amirale qt Date: Mon, 27 Apr 2020 06:54:46 +0000 Subject: [PATCH 087/482] Translated using Weblate (Esperanto) Currently translated at 100.0% (632 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/eo/ --- locales/eo.json | 70 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index d778938e9..6fb758fd1 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -508,15 +508,15 @@ "diagnosis_basesystem_ynh_main_version": "Servilo funkcias YunoHost {main_version} ({repo})", "diagnosis_basesystem_ynh_inconsistent_versions": "Vi prizorgas malkonsekvencajn versiojn de la YunoHost-pakoj... plej probable pro malsukcesa aŭ parta ĝisdatigo.", "diagnosis_display_tip_web": "Vi povas iri al la sekcio Diagnozo (en la hejmekrano) por vidi la trovitajn problemojn.", - "diagnosis_cache_still_valid": "(Kaŝmemoro ankoraŭ validas por {category} diagnozo. Ankoraŭ ne re-diagnoza!)", + "diagnosis_cache_still_valid": "(La kaŝmemoro ankoraŭ validas por {category} diagnozo. Vi ankoraŭ ne diagnozas ĝin!)", "diagnosis_cant_run_because_of_dep": "Ne eblas fari diagnozon por {category} dum estas gravaj problemoj rilataj al {dep}.", "diagnosis_display_tip_cli": "Vi povas aranĝi 'yunohost diagnosis show --issues' por aperigi la trovitajn problemojn.", "diagnosis_failed_for_category": "Diagnozo malsukcesis por kategorio '{category}': {error}", "app_upgrade_script_failed": "Eraro okazis en la skripto pri ĝisdatiga programo", - "diagnosis_diskusage_verylow": "Stokado {mountpoint} (sur aparato {device)) restas nur {free} ({free_percent}%) spaco. Vi vere konsideru purigi iom da spaco.", + "diagnosis_diskusage_verylow": "Stokado {mountpoint} (sur aparato {device} ) nur restas {{free} ({free_percent}%) spaco restanta (el {total}). Vi vere konsideru purigi iom da spaco !", "diagnosis_ram_verylow": "La sistemo nur restas {available} ({available_percent}%) RAM! (el {total})", "diagnosis_mail_outgoing_port_25_blocked": "Eliranta haveno 25 ŝajnas esti blokita. Vi devas provi malŝlosi ĝin en via agorda panelo de provizanto (aŭ gastiganto). Dume la servilo ne povos sendi retpoŝtojn al aliaj serviloj.", - "diagnosis_http_bad_status_code": "La diagnoza sistemo ne povis atingi vian servilon. Povas esti, ke alia maŝino respondis anstataŭ via servilo. Vi devus kontroli, ke vi ĝuste redonas la havenon 80, ke via agordo de nginx estas ĝisdatigita kaj ke reverso-prokuro ne interbatalas.", + "diagnosis_http_bad_status_code": "Ĝi aspektas kiel alia maŝino (eble via interreta enkursigilo) respondita anstataŭ via servilo.
1. La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 80 (kaj 443) ne estas ĝuste senditaj al via servilo .
2. Pri pli kompleksaj agordoj: certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", "main_domain_changed": "La ĉefa domajno estis ŝanĝita", "yunohost_postinstall_end_tip": "La post-instalado finiĝis! Por fini vian agordon, bonvolu konsideri:\n - aldonado de unua uzanto tra la sekcio 'Uzantoj' de la retadreso (aŭ 'uzanto de yunohost kreu ' en komandlinio);\n - diagnozi eblajn problemojn per la sekcio 'Diagnozo' de la reteja administrado (aŭ 'diagnoza yunohost-ekzekuto' en komandlinio);\n - legante la partojn 'Finigi vian agordon' kaj 'Ekkoni Yunohost' en la administra dokumentado: https://yunohost.org/admindoc.", "migration_description_0014_remove_app_status_json": "Forigi heredajn dosierojn", @@ -526,21 +526,21 @@ "diagnosis_ip_no_ipv6": "La servilo ne havas funkciantan IPv6.", "diagnosis_ip_not_connected_at_all": "La servilo tute ne ŝajnas esti konektita al la Interreto !?", "diagnosis_ip_dnsresolution_working": "Rezolucio pri domajna nomo funkcias !", - "diagnosis_ip_weird_resolvconf": "DNS-rezolucio ŝajnas funkcii, sed atentu, ke vi ŝajnas uzi kutimon /etc/resolv.conf.", - "diagnosis_ip_weird_resolvconf_details": "Anstataŭe, ĉi tiu dosiero estu ligilo kun /etc/resolvconf/run/resolv.conf mem montrante al 127.0.0.1 (dnsmasq). La efektivaj solvantoj devas agordi en /etc/resolv.dnsmasq.conf.", - "diagnosis_dns_good_conf": "Bona DNS-agordo por domajno {domain} (kategorio {category})", - "diagnosis_dns_bad_conf": "Malbona aŭ mankas DNS-agordo por domajno {domain} (kategorio {category})", + "diagnosis_ip_weird_resolvconf": "DNS-rezolucio ŝajnas funkcii, sed ŝajnas ke vi uzas kutiman /etc/resolv.conf .", + "diagnosis_ip_weird_resolvconf_details": "La dosiero /etc/resolv.conf devas esti ligilo al /etc/resolvconf/run/resolv.conf indikante 127.0.0.1 (dnsmasq). Se vi volas permane agordi DNS-solvilojn, bonvolu redakti /etc/resolv.dnsmasq.conf .", + "diagnosis_dns_good_conf": "DNS-registroj estas ĝuste agorditaj por domajno {domain} (kategorio {category})", + "diagnosis_dns_bad_conf": "Iuj DNS-registroj mankas aŭ malĝustas por domajno {domain} (kategorio {category})", "diagnosis_ram_ok": "La sistemo ankoraŭ havas {available} ({available_percent}%) RAM forlasita de {total}.", "diagnosis_swap_none": "La sistemo tute ne havas interŝanĝon. Vi devus pripensi aldoni almenaŭ {recommended} da interŝanĝo por eviti situaciojn en kiuj la sistemo restas sen memoro.", "diagnosis_swap_notsomuch": "La sistemo havas nur {total}-interŝanĝon. Vi konsideru havi almenaŭ {recommended} por eviti situaciojn en kiuj la sistemo restas sen memoro.", - "diagnosis_regenconf_manually_modified_details": "Ĉi tio probable estas bona tiel longe kiel vi scias kion vi faras;)!", + "diagnosis_regenconf_manually_modified_details": "Ĉi tio probable estas bona, se vi scias, kion vi faras! YunoHost ĉesigos ĝisdatigi ĉi tiun dosieron aŭtomate ... Sed atentu, ke YunoHost-ĝisdatigoj povus enhavi gravajn rekomendajn ŝanĝojn. Se vi volas, vi povas inspekti la diferencojn per yyunohost tools regen-conf {category} --dry-run --with-diff kaj devigi la reset al la rekomendita agordo per yunohost tools regen-conf {category} --force", "diagnosis_regenconf_manually_modified_debian": "Agordodosiero {file} estis modifita permane kompare kun la defaŭlta Debian.", "diagnosis_regenconf_manually_modified_debian_details": "Ĉi tio probable estas bona, sed devas observi ĝin...", "diagnosis_security_all_good": "Neniu kritika sekureca vundebleco estis trovita.", "diagnosis_security_vulnerable_to_meltdown": "Vi ŝajnas vundebla al la kritiko-vundebleco de Meltdown", "diagnosis_no_cache": "Neniu diagnoza kaŝmemoro por kategorio '{category}'", "diagnosis_ip_broken_dnsresolution": "Rezolucio pri domajna nomo rompiĝas pro iu kialo... Ĉu fajroŝirmilo blokas DNS-petojn ?", - "diagnosis_ip_broken_resolvconf": "Rezolucio pri domajna nomo ŝajnas esti rompita en via servilo, kiu ŝajnas rilata al /etc/resolv.conf ne notante 127.0.0.1.", + "diagnosis_ip_broken_resolvconf": "Rezolucio pri domajna nomo estas rompita en via servilo, kiu ŝajnas rilata al /etc/resolv.conf ne montrante al 127.0.0.1 .", "diagnosis_dns_missing_record": "Laŭ la rekomendita DNS-agordo, vi devas aldoni DNS-registron kun\ntipo: {type}\nnomo: {name}\nvaloro: {value}", "diagnosis_dns_discrepancy": "La DNS-registro kun tipo {type} kaj nomo {name} ne kongruas kun la rekomendita agordo.\nNuna valoro: {current}\nEsceptita valoro: {value}", "diagnosis_services_conf_broken": "Agordo estas rompita por servo {service} !", @@ -549,7 +549,7 @@ "diagnosis_swap_ok": "La sistemo havas {total} da interŝanĝoj!", "diagnosis_mail_ougoing_port_25_ok": "Eliranta haveno 25 ne estas blokita kaj retpoŝto povas esti sendita al aliaj serviloj.", "diagnosis_regenconf_allgood": "Ĉiuj agordaj dosieroj kongruas kun la rekomendita agordo!", - "diagnosis_regenconf_manually_modified": "Agordodosiero {file} estis permane modifita.", + "diagnosis_regenconf_manually_modified": "Agordodosiero {file} ŝajnas esti permane modifita.", "diagnosis_description_ip": "Interreta konektebleco", "diagnosis_description_dnsrecords": "Registroj DNS", "diagnosis_description_services": "Servo kontrolas staton", @@ -557,27 +557,27 @@ "diagnosis_description_security": "Sekurecaj kontroloj", "diagnosis_ports_could_not_diagnose": "Ne povis diagnozi, ĉu haveblaj havenoj de ekstere.", "diagnosis_ports_could_not_diagnose_details": "Eraro: {error}", - "diagnosis_services_bad_status_tip": "Vi povas provi rekomenci la servon, kaj se ĝi ne funkcias, trarigardu la servajn protokolojn uzante 'yunohost service log {service}' aŭ tra la sekcio 'Servoj' de la retadreso.", + "diagnosis_services_bad_status_tip": "Vi povas provi rekomenci la servon , kaj se ĝi ne funkcias, rigardu La servaj registroj en reteja (el la komandlinio, vi povas fari tion per yunohost service restart {service} kaj yunohost service log {service} ).", "diagnosis_security_vulnerable_to_meltdown_details": "Por ripari tion, vi devas ĝisdatigi vian sistemon kaj rekomenci por ŝarĝi la novan linux-kernon (aŭ kontaktu vian servilan provizanton se ĉi tio ne funkcias). Vidu https://meltdownattack.com/ por pliaj informoj.", "diagnosis_description_basesystem": "Baza sistemo", "diagnosis_description_regenconf": "Sistemaj agordoj", "main_domain_change_failed": "Ne eblas ŝanĝi la ĉefan domajnon", "log_domain_main_domain": "Faru de '{}' la ĉefa domajno", - "diagnosis_http_timeout": "Tempolimigita dum provado kontakti vian servilon de ekstere. Ĝi ŝajnas esti neatingebla. Vi devus kontroli, ke vi ĝuste redonas la havenon 80, ke nginx funkcias kaj ke fajroŝirmilo ne interbatalas.", + "diagnosis_http_timeout": "Tempolimigita dum provado kontakti vian servilon de ekstere. Ĝi ŝajnas esti neatingebla.
1. La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 80 (kaj 443) ne estas ĝuste senditaj al via servilo.
2. Vi ankaŭ devas certigi, ke la servo nginx funkcias
3. Pri pli kompleksaj agordoj: certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", "diagnosis_http_connection_error": "Rilata eraro: ne povis konektiĝi al la petita domajno, tre probable ĝi estas neatingebla.", "migration_description_0013_futureproof_apps_catalog_system": "Migru al la nova katalogosistemo pri estontecaj programoj", "diagnosis_ignored_issues": "(+ {nb_ignored} ignorataj aferoj))", "diagnosis_found_errors": "Trovis {errors} signifa(j) afero(j) rilata al {category}!", "diagnosis_found_errors_and_warnings": "Trovis {errors} signifaj problemo (j) (kaj {warnings} averto) rilataj al {category}!", - "diagnosis_diskusage_low": "Stokado {mountpoint} (sur aparato {device)) restas nur {free} ({free_percent}%) spaco. Estu zorgema.", - "diagnosis_diskusage_ok": "Stokado {mountpoint} (sur aparato {device) ankoraŭ restas {free} ({free_percent}%) spaco!", + "diagnosis_diskusage_low": "Stokado {mountpoint} (sur aparato {device} ) nur restas {{free} ({free_percent}%) spaco restanta (el {total}). Estu zorgema.", + "diagnosis_diskusage_ok": "Stokado {mountpoint} (sur aparato {device}) ankoraŭ restas {free} ({free_percent}%) spaco (el {total})!", "global_settings_setting_pop3_enabled": "Ebligu la protokolon POP3 por la poŝta servilo", "diagnosis_unknown_categories": "La jenaj kategorioj estas nekonataj: {categories}", "diagnosis_services_running": "Servo {service} funkcias!", "diagnosis_ports_unreachable": "Haveno {port} ne atingeblas de ekstere.", "diagnosis_ports_ok": "Haveno {port} atingeblas de ekstere.", "diagnosis_ports_needed_by": "Eksponi ĉi tiun havenon necesas por {category} funkcioj (servo {service})", - "diagnosis_ports_forwarding_tip": "Por solvi ĉi tiun problemon, vi plej verŝajne bezonas agordi havenon en via interreta enkursigilo kiel priskribite en https://yunohost.org/isp_box_config", + "diagnosis_ports_forwarding_tip": "Por solvi ĉi tiun problemon, vi plej verŝajne devas agordi la plusendon de haveno en via interreta enkursigilo kiel priskribite en https://yunohost.org/isp_box_config", "diagnosis_http_could_not_diagnose": "Ne povis diagnozi, ĉu atingeblas domajno de ekstere.", "diagnosis_http_could_not_diagnose_details": "Eraro: {error}", "diagnosis_http_ok": "Domajno {domain} atingebla per HTTP de ekster la loka reto.", @@ -598,5 +598,43 @@ "diagnosis_basesystem_hardware_board": "Servilo-tabulo-modelo estas {model}", "diagnosis_description_web": "Reta", "domain_cannot_add_xmpp_upload": "Vi ne povas aldoni domajnojn per 'xmpp-upload'. Ĉi tiu speco de nomo estas rezervita por la XMPP-alŝuta funkcio integrita en YunoHost.", - "group_already_exist_on_system_but_removing_it": "Grupo {group} jam ekzistas en la sistemaj grupoj, sed YunoHost forigos ĝin …" + "group_already_exist_on_system_but_removing_it": "Grupo {group} jam ekzistas en la sistemaj grupoj, sed YunoHost forigos ĝin …", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Iuj provizantoj ne lasos vin malŝlosi elirantan havenon 25 ĉar ili ne zorgas pri Neta Neŭtraleco.
- Iuj el ili provizas la alternativon de uzante retpoŝtan servilon kvankam ĝi implicas, ke la relajso povos spioni vian retpoŝtan trafikon.
- Amika privateco estas uzi VPN * kun dediĉita publika IP * por pretervidi ĉi tiun specon. de limoj. Vidu https://yunohost.org/#/vpn_avantage
- Vi ankaŭ povas konsideri ŝanĝi al pli neta neŭtraleco-amika provizanto ", + "diagnosis_mail_fcrdns_nok_details": "Vi unue provu agordi la inversan DNS kun {ehlo_domain} en via interreta enkursigilo aŭ en via retprovizanta interfaco. (Iuj gastigantaj provizantoj eble postulas, ke vi sendu al ili subtenan bileton por ĉi tio).", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Iuj provizantoj ne lasos vin agordi vian inversan DNS (aŭ ilia funkcio povus esti rompita ...). Se vi spertas problemojn pro tio, konsideru jenajn solvojn:
- Iuj ISP provizas la alternativon de uzante retpoŝtan servilon kvankam ĝi implicas, ke la relajso povos spioni vian retpoŝtan trafikon.
- Interreta privateco estas uzi VPN * kun dediĉita publika IP * por preterpasi ĉi tiajn limojn. Vidu
https://yunohost.org/#/vpn_avantage
- Finfine eblas ankaŭ ŝanĝo de provizanto ", + "diagnosis_display_tip": "Por vidi la trovitajn problemojn, vi povas iri al la sekcio pri Diagnozo de la reteja administrado, aŭ funkcii \"yunohost diagnosis show --issues\" el la komandlinio.", + "diagnosis_ip_global": "Tutmonda IP: {global} ", + "diagnosis_ip_local": "Loka IP: {local} ", + "diagnosis_dns_point_to_doc": "Bonvolu kontroli la dokumentaron ĉe https://yunohost.org/dns_config se vi bezonas helpon pri agordo de DNS-registroj.", + "diagnosis_mail_outgoing_port_25_ok": "La SMTP-poŝta servilo kapablas sendi retpoŝtojn (eliranta haveno 25 ne estas blokita).", + "diagnosis_mail_outgoing_port_25_blocked_details": "Vi unue provu malŝlosi elirantan havenon 25 en via interreta enkursigilo aŭ en via retprovizanta interfaco. (Iuj gastigantaj provizantoj eble postulas, ke vi sendu al ili subtenan bileton por ĉi tio).", + "diagnosis_mail_ehlo_unreachable": "La SMTP-poŝta servilo estas neatingebla de ekstere sur IPv {ipversion}. Ĝi ne povos ricevi retpoŝtojn.", + "diagnosis_mail_ehlo_ok": "La SMTP-poŝta servilo atingeblas de ekstere kaj tial kapablas ricevi retpoŝtojn !", + "diagnosis_mail_ehlo_unreachable_details": "Ne povis malfermi rilaton sur la haveno 25 al via servilo en IPv {ipversion}. Ĝi ŝajnas esti neatingebla.
1. La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 25 ne estas ĝuste sendita al via servilo .
2. Vi ankaŭ devas certigi, ke servo-prefikso funkcias.
3. Pri pli kompleksaj agordoj: certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", + "diagnosis_mail_ehlo_bad_answer": "Ne-SMTP-servo respondita sur la haveno 25 sur IPv {ipversion}", + "diagnosis_mail_ehlo_bad_answer_details": "Povas esti ke alia maŝino respondas anstataŭ via servilo.", + "diagnosis_mail_ehlo_wrong": "Malsama SMTP-poŝta servilo respondas pri IPv {ipversion}. Via servilo probable ne povos ricevi retpoŝtojn.", + "diagnosis_mail_ehlo_wrong_details": "La EHLO ricevita de la fora diagnozilo en IPv {ipversion} diferencas de la domajno de via servilo.
Ricevita EHLO: {wrong_ehlo}
Atendita: {right_ehlo}
La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 25 ne estas ĝuste sendita al via servilo . Alternative, certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", + "diagnosis_mail_ehlo_could_not_diagnose": "Ne povis diagnozi ĉu postfiksa poŝta servilo atingebla de ekstere en IPv {ipversion}.", + "diagnosis_mail_ehlo_could_not_diagnose_details": "Eraro: {error}", + "diagnosis_mail_fcrdns_ok": "Via inversa DNS estas ĝuste agordita!", + "diagnosis_mail_fcrdns_dns_missing": "Neniu inversa DNS estas difinita en IPv {ipversion}. Iuj retpoŝtoj povas malsukcesi liveri aŭ povus esti markitaj kiel spamo.", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Iuj provizantoj ne lasos vin agordi vian inversan DNS (aŭ ilia funkcio povus esti rompita ...). Se via inversa DNS estas ĝuste agordita por IPv4, vi povas provi malebligi la uzon de IPv6 kiam vi sendas retpoŝtojn per funkciado yunohost-agordoj set smtp.allow_ipv6 -v off . Noto: ĉi tiu lasta solvo signifas, ke vi ne povos sendi aŭ ricevi retpoŝtojn de la malmultaj IPv6-nur serviloj tie.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain": "La inversa DNS ne ĝuste agordis en IPv {ipversion}. Iuj retpoŝtoj povas malsukcesi liveri aŭ povus esti markitaj kiel spamo.", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Aktuala reverso DNS: {rdns_domain}
Atendita valoro: {ehlo_domain}", + "diagnosis_mail_blacklist_ok": "La IP kaj domajnoj uzataj de ĉi tiu servilo ne ŝajnas esti listigitaj nigre", + "diagnosis_mail_blacklist_listed_by": "Via IP aŭ domajno {item} estas listigita en {blacklist_name}", + "diagnosis_mail_blacklist_reason": "La negra listo estas: {reason}", + "diagnosis_mail_blacklist_website": "Post identigi kial vi listigas kaj riparis ĝin, bonvolu peti forigi vian IP aŭ domenion sur {blacklist_website}", + "diagnosis_mail_queue_ok": "{nb_pending} pritraktataj retpoŝtoj en la retpoŝtaj vostoj", + "diagnosis_mail_queue_unavailable": "Ne povas konsulti multajn pritraktitajn retpoŝtojn en vosto", + "diagnosis_mail_queue_unavailable_details": "Eraro: {error}", + "diagnosis_mail_queue_too_big": "Tro multaj pritraktataj retpoŝtoj en retpoŝto ({nb_pending} retpoŝtoj)", + "diagnosis_ports_partially_unreachable": "Haveno {port} ne atingebla de ekstere en IPv {failed}.", + "diagnosis_http_hairpinning_issue": "Via loka reto ŝajne ne havas haŭtadon.", + "diagnosis_http_hairpinning_issue_details": "Ĉi tio probable estas pro via ISP-skatolo / enkursigilo. Rezulte, homoj de ekster via loka reto povos aliri vian servilon kiel atendite, sed ne homoj de interne de la loka reto (kiel vi, probable?) Kiam uzas la domajnan nomon aŭ tutmondan IP. Eble vi povas plibonigi la situacion per rigardado al https://yunohost.org/dns_local_network", + "diagnosis_http_partially_unreachable": "Domajno {domain} ŝajnas neatingebla per HTTP de ekster la loka reto en IPv {failed}, kvankam ĝi funkcias en IPv {passed}.", + "diagnosis_http_nginx_conf_not_up_to_date": "La nginx-agordo de ĉi tiu domajno ŝajnas esti modifita permane, kaj malhelpas YunoHost diagnozi ĉu ĝi atingeblas per HTTP.", + "diagnosis_http_nginx_conf_not_up_to_date_details": "Por solvi la situacion, inspektu la diferencon per la komandlinio per yunohost tools regen-conf nginx --dry-run --with-diff kaj se vi aranĝas, apliku la ŝanĝojn per yunohost tools regen-conf nginx --force.", + "global_settings_setting_smtp_allow_ipv6": "Permesu la uzon de IPv6 por ricevi kaj sendi poŝton" } From 31426c54698fd2991b13b3cba40c83779017bf5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Idafe=20Hern=C3=A1ndez?= Date: Sun, 3 May 2020 19:38:34 +0000 Subject: [PATCH 088/482] Translated using Weblate (Spanish) Currently translated at 92.7% (586 of 632 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/es/ --- locales/es.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index 6d77dd2ef..cd0d1dc57 100644 --- a/locales/es.json +++ b/locales/es.json @@ -601,5 +601,9 @@ "diagnosis_ports_forwarding_tip": "Para solucionar este incidente, debería configurar el \"port forwading\" en su router como especificado en https://yunohost.org/isp_box_config", "certmanager_warning_subdomain_dns_record": "El subdominio '{subdomain:s}' no se resuelve en la misma dirección IP que '{domain:s}'. Algunas funciones no estarán disponibles hasta que solucione esto y regenere el certificado.", "domain_cannot_add_xmpp_upload": "No puede agregar dominios que comiencen con 'xmpp-upload'. Este tipo de nombre está reservado para la función de carga XMPP integrada en YunoHost.", - "yunohost_postinstall_end_tip": "¡La post-instalación completada! Para finalizar su configuración, considere:\n - agregar un primer usuario a través de la sección 'Usuarios' del webadmin (o 'yunohost user create ' en la línea de comandos);\n - diagnostique problemas potenciales a través de la sección 'Diagnóstico' de webadmin (o 'ejecución de diagnóstico yunohost' en la línea de comandos);\n - leyendo las partes 'Finalizando su configuración' y 'Conociendo a Yunohost' en la documentación del administrador: https://yunohost.org/admindoc." + "yunohost_postinstall_end_tip": "¡La post-instalación completada! Para finalizar su configuración, considere:\n - agregar un primer usuario a través de la sección 'Usuarios' del webadmin (o 'yunohost user create ' en la línea de comandos);\n - diagnostique problemas potenciales a través de la sección 'Diagnóstico' de webadmin (o 'ejecución de diagnóstico yunohost' en la línea de comandos);\n - leyendo las partes 'Finalizando su configuración' y 'Conociendo a Yunohost' en la documentación del administrador: https://yunohost.org/admindoc.", + "diagnosis_dns_point_to_doc": "Por favor, consulta la documentación en https://yunohost.org/dns_config si necesitas ayuda para configurar los registros DNS.", + "diagnosis_ip_global": "IP Global: {global}", + "diagnosis_mail_outgoing_port_25_ok": "El servidor de email SMTP puede mandar emails (puerto saliente 25 no está bloqueado).", + "diagnosis_mail_outgoing_port_25_blocked_details": "Deberías intentar desbloquear el puerto 25 saliente en la interfaz de tu router o en la interfaz de tu provedor de hosting. (Algunos hosting pueden necesitar que les abras un ticket de soporte para esto)." } From da7d0d0561d40e3535ded4b002337daa027b2663 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Fri, 8 May 2020 22:11:02 +0000 Subject: [PATCH 089/482] Translated using Weblate (Catalan) Currently translated at 98.3% (630 of 641 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index e5174205d..234a32fe4 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -636,5 +636,7 @@ "diagnosis_mail_fcrdns_nok_details": "Hauríeu d'intentar configurar primer el DNS invers amb {ehlo_domain} en la interfície del router o en la interfície del vostre allotjador. (Alguns allotjadors requereixen que obris un informe de suport per això).", "diagnosis_mail_fcrdns_nok_alternatives_4": "Alguns proveïdors no permeten configurar el DNS invers (o aquesta funció pot no funcionar…). Si teniu problemes a causa d'això, considereu les solucions següents:
- Alguns proveïdors d'accés a internet (ISP) donen l'alternativa de utilitzar un relay de servidor de correu electrònic tot i que implica que el relay podrà espiar el trànsit de correus electrònics.
- Una alternativa respectuosa amb la privacitat és utilitzar una VPN *amb una IP pública dedicada* per sobrepassar aquest tipus de limitacions. Mireu https://yunohost.org/#/vpn_advantage
- Finalment, també es pot canviar de proveïdor", "diagnosis_mail_fcrdns_nok_alternatives_6": "Alguns proveïdors no permeten configurar el vostre DNS invers (o la funció no els hi funciona…). Si el vostre DNS invers està correctament configurat per IPv4, podeu intentar deshabilitar l'ús de IPv6 per a enviar correus electrònics utilitzant yunohost settings set smtp.allow_ipv6 -v off. Nota: aquesta última solució implica que no podreu enviar o rebre correus electrònics cap a els pocs servidors que hi ha que només tenen IPv-6.", - "diagnosis_http_hairpinning_issue_details": "Això és probablement a causa del router del vostre proveïdor d'accés a internet. El que fa, que gent de fora de la xarxa local pugui accedir al servidor sense problemes, però no la gent de dins la xarxa local (com vostè probablement) quan s'utilitza el nom de domini o la IP global. Podreu segurament millorar la situació fent una ullada a https://yunohost.org/dns_local_network" + "diagnosis_http_hairpinning_issue_details": "Això és probablement a causa del router del vostre proveïdor d'accés a internet. El que fa, que gent de fora de la xarxa local pugui accedir al servidor sense problemes, però no la gent de dins la xarxa local (com vostè probablement) quan s'utilitza el nom de domini o la IP global. Podreu segurament millorar la situació fent una ullada a https://yunohost.org/dns_local_network", + "backup_archive_cant_retrieve_info_json": "No s'ha pogut carregar la informació de l'arxiu «{archive}»… No s'ha pogut obtenir el fitxer info.json (o no és un fitxer json vàlid).", + "backup_archive_corrupted": "Sembla que l'arxiu de la còpia de seguretat «{archive}» està corromput : {error}" } From d3252a1739f4a097372c83a9444e60056705ac84 Mon Sep 17 00:00:00 2001 From: clecle226 Date: Thu, 7 May 2020 15:45:26 +0000 Subject: [PATCH 090/482] Translated using Weblate (French) Currently translated at 100.0% (641 of 641 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index e9402730d..bf5598f75 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -54,7 +54,7 @@ "domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", "domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", "domain_exists": "Le domaine existe déjà", - "domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d’abord les désinstaller avant de supprimer ce domaine", + "domain_uninstall_app_first": "Ces applications sont toujours installées sur votre domaine: {apps}. Veuillez d’abord les désinstaller avant de supprimer ce domaine", "domain_unknown": "Domaine inconnu", "done": "Terminé", "downloading": "Téléchargement en cours …", @@ -184,7 +184,7 @@ "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.", + "certmanager_acme_not_configured_for_domain": "Le challenge ACME n'a pas pu être validé pour le domaine {domain} pour le moment car le code de la configuration nginx est manquant... Merci de vérifier que votre configuration nginx est à jour avec la commande: `yunohost tools regen-conf nginx --dry-run --with-diff`.", "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.", "domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (cela n’en causera peut-être pas).", @@ -637,5 +637,15 @@ "diagnosis_http_hairpinning_issue_details": "C'est probablement à cause de la box/routeur de votre fournisseur d'accès internet. Par conséquent, les personnes extérieures à votre réseau local pourront accéder à votre serveur comme prévu, mais pas les personnes internes au réseau local (comme vous, probablement ?) si elles utilisent le nom de domaine ou l'IP globale. Vous pourrez peut-être améliorer la situation en consultant https://yunohost.org/dns_local_network", "diagnosis_http_partially_unreachable": "Le domaine {domain} semble inaccessible en HTTP depuis l’extérieur du réseau local en IPv{failed}, bien qu’il fonctionne en IPv{passed}.", "diagnosis_http_nginx_conf_not_up_to_date": "La configuration Nginx de ce domaine semble avoir été modifiée manuellement et empêche YunoHost de diagnostiquer si elle est accessible en HTTP.", - "diagnosis_http_nginx_conf_not_up_to_date_details": "Pour corriger la situation, inspectez la différence avec la ligne de commande en utilisant les outils yunohost regen-conf nginx --dry-run --with-diff et si vous êtes d’accord, appliquez les modifications avec yunohost tools regen-conf nginx --force." + "diagnosis_http_nginx_conf_not_up_to_date_details": "Pour corriger la situation, inspectez la différence avec la ligne de commande en utilisant les outils yunohost regen-conf nginx --dry-run --with-diff et si vous êtes d’accord, appliquez les modifications avec yunohost tools regen-conf nginx --force.", + "backup_archive_cant_retrieve_info_json": "Impossible d'avoir des informations sur l'archive '{archive}' ... Le fichier info.json ne peut pas être trouvé (ou n'est pas un fichier json valide).", + "backup_archive_corrupted": "Il semble que l'archive de la sauvegarde '{archive}' est corrompue : {error}", + "diagnosis_ip_no_ipv6_tip": "L'utilisation de IPv6 n'est pas obligatoire pour le fonctionnement de votre serveur, mais cela permet un meilleur fonctionnement de l'internet dans son ensemble. IPv6 peut-être automatiquement configuré par votre système ou votre FAI s'il est disponible. Autrement, vous devrez prendre quelque minute pour le configurer manuellement comme il est écrit dans cette documentation: https://yunohost.org/#/ipv6. Si vous ne pouvez pas activer IPv6 ou si c'est trop technique pour vous, vous pouvez ignorer cet avertissement sans problème.", + "diagnosis_domain_expiration_not_found": "Impossible de vérifier la date d'expiration de certains domaines", + "diagnosis_domain_expiration_not_found_details": "Les informations WHOIS pour le domaine {domain} ne semblent pas contenir les informations concernant la date d'expiration ?", + "diagnosis_domain_not_found_details": "Le domaine {domain} n'existe pas dans la base de donnée WHOIS ou est expiré !", + "diagnosis_domain_expiration_success": "Vos domaines sont enregistrés et ne sont pas expirés prochainement.", + "diagnosis_domain_expiration_warning": "Certains domaines vont expirés prochainement !", + "diagnosis_domain_expiration_error": "Certains domaines vont expirés TRÈS PROCHAINEMENT !", + "diagnosis_domain_expires_in": "Le {domain} expire dans {days} jours." } From 1577d0e8439d2606134e8c373a5bc8680a644c2b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 May 2020 20:35:33 +0200 Subject: [PATCH 091/482] Stupid spaces issues --- locales/eo.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 6fb758fd1..f093633a5 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -248,7 +248,7 @@ "ldap_init_failed_to_create_admin": "LDAP-iniciato ne povis krei administran uzanton", "backup_output_directory_required": "Vi devas provizi elirejan dosierujon por la sekurkopio", "tools_upgrade_cant_unhold_critical_packages": "Ne povis malŝalti kritikajn pakojn…", - "log_link_to_log": "Plena ŝtipo de ĉi tiu operacio: ' {desc} '", + "log_link_to_log": "Plena ŝtipo de ĉi tiu operacio: '{desc} '", "global_settings_cant_serialize_settings": "Ne eblis serialigi datumojn pri agordoj, motivo: {reason:s}", "backup_running_hooks": "Kurado de apogaj hokoj …", "certmanager_domain_unknown": "Nekonata domajno '{domain:s}'", @@ -351,7 +351,7 @@ "dyndns_ip_update_failed": "Ne povis ĝisdatigi IP-adreson al DynDNS", "migration_description_0004_php5_to_php7_pools": "Rekonfigu la PHP-naĝejojn por uzi PHP 7 anstataŭ 5", "ssowat_conf_updated": "SSOwat-agordo ĝisdatigita", - "log_link_to_failed_log": "Ne povis plenumi la operacion '{desc}'. Bonvolu provizi la plenan protokolon de ĉi tiu operacio per alklakante ĉi tie por akiri helpon", + "log_link_to_failed_log": "Ne povis plenumi la operacion '{desc}'. Bonvolu provizi la plenan protokolon de ĉi tiu operacio per alklakante ĉi tie por akiri helpon", "user_home_creation_failed": "Ne povis krei dosierujon \"home\" por uzanto", "pattern_backup_archive_name": "Devas esti valida dosiernomo kun maksimume 30 signoj, alfanombraj kaj -_. signoj nur", "restore_cleaning_failed": "Ne eblis purigi la adresaron de provizora restarigo", @@ -513,10 +513,10 @@ "diagnosis_display_tip_cli": "Vi povas aranĝi 'yunohost diagnosis show --issues' por aperigi la trovitajn problemojn.", "diagnosis_failed_for_category": "Diagnozo malsukcesis por kategorio '{category}': {error}", "app_upgrade_script_failed": "Eraro okazis en la skripto pri ĝisdatiga programo", - "diagnosis_diskusage_verylow": "Stokado {mountpoint} (sur aparato {device} ) nur restas {{free} ({free_percent}%) spaco restanta (el {total}). Vi vere konsideru purigi iom da spaco !", + "diagnosis_diskusage_verylow": "Stokado {mountpoint} (sur aparato {device} ) nur restas {free} ({free_percent}%) spaco restanta (el {total}). Vi vere konsideru purigi iom da spaco !", "diagnosis_ram_verylow": "La sistemo nur restas {available} ({available_percent}%) RAM! (el {total})", "diagnosis_mail_outgoing_port_25_blocked": "Eliranta haveno 25 ŝajnas esti blokita. Vi devas provi malŝlosi ĝin en via agorda panelo de provizanto (aŭ gastiganto). Dume la servilo ne povos sendi retpoŝtojn al aliaj serviloj.", - "diagnosis_http_bad_status_code": "Ĝi aspektas kiel alia maŝino (eble via interreta enkursigilo) respondita anstataŭ via servilo.
1. La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 80 (kaj 443) ne estas ĝuste senditaj al via servilo .
2. Pri pli kompleksaj agordoj: certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", + "diagnosis_http_bad_status_code": "Ĝi aspektas kiel alia maŝino (eble via interreta enkursigilo) respondita anstataŭ via servilo.
1. La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 80 (kaj 443) ne estas ĝuste senditaj al via servilo .
2. Pri pli kompleksaj agordoj: certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", "main_domain_changed": "La ĉefa domajno estis ŝanĝita", "yunohost_postinstall_end_tip": "La post-instalado finiĝis! Por fini vian agordon, bonvolu konsideri:\n - aldonado de unua uzanto tra la sekcio 'Uzantoj' de la retadreso (aŭ 'uzanto de yunohost kreu ' en komandlinio);\n - diagnozi eblajn problemojn per la sekcio 'Diagnozo' de la reteja administrado (aŭ 'diagnoza yunohost-ekzekuto' en komandlinio);\n - legante la partojn 'Finigi vian agordon' kaj 'Ekkoni Yunohost' en la administra dokumentado: https://yunohost.org/admindoc.", "migration_description_0014_remove_app_status_json": "Forigi heredajn dosierojn", @@ -526,21 +526,21 @@ "diagnosis_ip_no_ipv6": "La servilo ne havas funkciantan IPv6.", "diagnosis_ip_not_connected_at_all": "La servilo tute ne ŝajnas esti konektita al la Interreto !?", "diagnosis_ip_dnsresolution_working": "Rezolucio pri domajna nomo funkcias !", - "diagnosis_ip_weird_resolvconf": "DNS-rezolucio ŝajnas funkcii, sed ŝajnas ke vi uzas kutiman /etc/resolv.conf .", - "diagnosis_ip_weird_resolvconf_details": "La dosiero /etc/resolv.conf devas esti ligilo al /etc/resolvconf/run/resolv.conf indikante 127.0.0.1 (dnsmasq). Se vi volas permane agordi DNS-solvilojn, bonvolu redakti /etc/resolv.dnsmasq.conf .", + "diagnosis_ip_weird_resolvconf": "DNS-rezolucio ŝajnas funkcii, sed ŝajnas ke vi uzas kutiman /etc/resolv.conf .", + "diagnosis_ip_weird_resolvconf_details": "La dosiero /etc/resolv.conf devas esti ligilo al /etc/resolvconf/run/resolv.conf indikante 127.0.0.1 (dnsmasq). Se vi volas permane agordi DNS-solvilojn, bonvolu redakti /etc/resolv.dnsmasq.conf .", "diagnosis_dns_good_conf": "DNS-registroj estas ĝuste agorditaj por domajno {domain} (kategorio {category})", "diagnosis_dns_bad_conf": "Iuj DNS-registroj mankas aŭ malĝustas por domajno {domain} (kategorio {category})", "diagnosis_ram_ok": "La sistemo ankoraŭ havas {available} ({available_percent}%) RAM forlasita de {total}.", "diagnosis_swap_none": "La sistemo tute ne havas interŝanĝon. Vi devus pripensi aldoni almenaŭ {recommended} da interŝanĝo por eviti situaciojn en kiuj la sistemo restas sen memoro.", "diagnosis_swap_notsomuch": "La sistemo havas nur {total}-interŝanĝon. Vi konsideru havi almenaŭ {recommended} por eviti situaciojn en kiuj la sistemo restas sen memoro.", - "diagnosis_regenconf_manually_modified_details": "Ĉi tio probable estas bona, se vi scias, kion vi faras! YunoHost ĉesigos ĝisdatigi ĉi tiun dosieron aŭtomate ... Sed atentu, ke YunoHost-ĝisdatigoj povus enhavi gravajn rekomendajn ŝanĝojn. Se vi volas, vi povas inspekti la diferencojn per yyunohost tools regen-conf {category} --dry-run --with-diff kaj devigi la reset al la rekomendita agordo per yunohost tools regen-conf {category} --force", + "diagnosis_regenconf_manually_modified_details": "Ĉi tio probable estas bona, se vi scias, kion vi faras! YunoHost ĉesigos ĝisdatigi ĉi tiun dosieron aŭtomate ... Sed atentu, ke YunoHost-ĝisdatigoj povus enhavi gravajn rekomendajn ŝanĝojn. Se vi volas, vi povas inspekti la diferencojn per yyunohost tools regen-conf {category} --dry-run --with-diff kaj devigi la reset al la rekomendita agordo per yunohost tools regen-conf {category} --force", "diagnosis_regenconf_manually_modified_debian": "Agordodosiero {file} estis modifita permane kompare kun la defaŭlta Debian.", "diagnosis_regenconf_manually_modified_debian_details": "Ĉi tio probable estas bona, sed devas observi ĝin...", "diagnosis_security_all_good": "Neniu kritika sekureca vundebleco estis trovita.", "diagnosis_security_vulnerable_to_meltdown": "Vi ŝajnas vundebla al la kritiko-vundebleco de Meltdown", "diagnosis_no_cache": "Neniu diagnoza kaŝmemoro por kategorio '{category}'", "diagnosis_ip_broken_dnsresolution": "Rezolucio pri domajna nomo rompiĝas pro iu kialo... Ĉu fajroŝirmilo blokas DNS-petojn ?", - "diagnosis_ip_broken_resolvconf": "Rezolucio pri domajna nomo estas rompita en via servilo, kiu ŝajnas rilata al /etc/resolv.conf ne montrante al 127.0.0.1 .", + "diagnosis_ip_broken_resolvconf": "Rezolucio pri domajna nomo estas rompita en via servilo, kiu ŝajnas rilata al /etc/resolv.conf ne montrante al 127.0.0.1 .", "diagnosis_dns_missing_record": "Laŭ la rekomendita DNS-agordo, vi devas aldoni DNS-registron kun\ntipo: {type}\nnomo: {name}\nvaloro: {value}", "diagnosis_dns_discrepancy": "La DNS-registro kun tipo {type} kaj nomo {name} ne kongruas kun la rekomendita agordo.\nNuna valoro: {current}\nEsceptita valoro: {value}", "diagnosis_services_conf_broken": "Agordo estas rompita por servo {service} !", @@ -557,19 +557,19 @@ "diagnosis_description_security": "Sekurecaj kontroloj", "diagnosis_ports_could_not_diagnose": "Ne povis diagnozi, ĉu haveblaj havenoj de ekstere.", "diagnosis_ports_could_not_diagnose_details": "Eraro: {error}", - "diagnosis_services_bad_status_tip": "Vi povas provi rekomenci la servon , kaj se ĝi ne funkcias, rigardu La servaj registroj en reteja (el la komandlinio, vi povas fari tion per yunohost service restart {service} kaj yunohost service log {service} ).", + "diagnosis_services_bad_status_tip": "Vi povas provi rekomenci la servon , kaj se ĝi ne funkcias, rigardu La servaj registroj en reteja (el la komandlinio, vi povas fari tion per yunohost service restart {service} kajyunohost service log {service}).", "diagnosis_security_vulnerable_to_meltdown_details": "Por ripari tion, vi devas ĝisdatigi vian sistemon kaj rekomenci por ŝarĝi la novan linux-kernon (aŭ kontaktu vian servilan provizanton se ĉi tio ne funkcias). Vidu https://meltdownattack.com/ por pliaj informoj.", "diagnosis_description_basesystem": "Baza sistemo", "diagnosis_description_regenconf": "Sistemaj agordoj", "main_domain_change_failed": "Ne eblas ŝanĝi la ĉefan domajnon", "log_domain_main_domain": "Faru de '{}' la ĉefa domajno", - "diagnosis_http_timeout": "Tempolimigita dum provado kontakti vian servilon de ekstere. Ĝi ŝajnas esti neatingebla.
1. La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 80 (kaj 443) ne estas ĝuste senditaj al via servilo.
2. Vi ankaŭ devas certigi, ke la servo nginx funkcias
3. Pri pli kompleksaj agordoj: certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", + "diagnosis_http_timeout": "Tempolimigita dum provado kontakti vian servilon de ekstere. Ĝi ŝajnas esti neatingebla.
1. La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 80 (kaj 443) ne estas ĝuste senditaj al via servilo.
2. Vi ankaŭ devas certigi, ke la servo nginx funkcias
3. Pri pli kompleksaj agordoj: certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", "diagnosis_http_connection_error": "Rilata eraro: ne povis konektiĝi al la petita domajno, tre probable ĝi estas neatingebla.", "migration_description_0013_futureproof_apps_catalog_system": "Migru al la nova katalogosistemo pri estontecaj programoj", "diagnosis_ignored_issues": "(+ {nb_ignored} ignorataj aferoj))", "diagnosis_found_errors": "Trovis {errors} signifa(j) afero(j) rilata al {category}!", "diagnosis_found_errors_and_warnings": "Trovis {errors} signifaj problemo (j) (kaj {warnings} averto) rilataj al {category}!", - "diagnosis_diskusage_low": "Stokado {mountpoint} (sur aparato {device} ) nur restas {{free} ({free_percent}%) spaco restanta (el {total}). Estu zorgema.", + "diagnosis_diskusage_low": "Stokado {mountpoint} (sur aparato {device}) nur restas {free} ({free_percent}%) spaco restanta (el {total}). Estu zorgema.", "diagnosis_diskusage_ok": "Stokado {mountpoint} (sur aparato {device}) ankoraŭ restas {free} ({free_percent}%) spaco (el {total})!", "global_settings_setting_pop3_enabled": "Ebligu la protokolon POP3 por la poŝta servilo", "diagnosis_unknown_categories": "La jenaj kategorioj estas nekonataj: {categories}", @@ -599,29 +599,29 @@ "diagnosis_description_web": "Reta", "domain_cannot_add_xmpp_upload": "Vi ne povas aldoni domajnojn per 'xmpp-upload'. Ĉi tiu speco de nomo estas rezervita por la XMPP-alŝuta funkcio integrita en YunoHost.", "group_already_exist_on_system_but_removing_it": "Grupo {group} jam ekzistas en la sistemaj grupoj, sed YunoHost forigos ĝin …", - "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Iuj provizantoj ne lasos vin malŝlosi elirantan havenon 25 ĉar ili ne zorgas pri Neta Neŭtraleco.
- Iuj el ili provizas la alternativon de uzante retpoŝtan servilon kvankam ĝi implicas, ke la relajso povos spioni vian retpoŝtan trafikon.
- Amika privateco estas uzi VPN * kun dediĉita publika IP * por pretervidi ĉi tiun specon. de limoj. Vidu https://yunohost.org/#/vpn_avantage
- Vi ankaŭ povas konsideri ŝanĝi al pli neta neŭtraleco-amika provizanto ", + "diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Iuj provizantoj ne lasos vin malŝlosi elirantan havenon 25 ĉar ili ne zorgas pri Neta Neŭtraleco.
- Iuj el ili provizas la alternativon de uzante retpoŝtan servilon kvankam ĝi implicas, ke la relajso povos spioni vian retpoŝtan trafikon.
- Amika privateco estas uzi VPN * kun dediĉita publika IP * por pretervidi ĉi tiun specon. de limoj. Vidu https://yunohost.org/#/vpn_avantage
- Vi ankaŭ povas konsideri ŝanĝi al pli neta neŭtraleco-amika provizanto", "diagnosis_mail_fcrdns_nok_details": "Vi unue provu agordi la inversan DNS kun {ehlo_domain} en via interreta enkursigilo aŭ en via retprovizanta interfaco. (Iuj gastigantaj provizantoj eble postulas, ke vi sendu al ili subtenan bileton por ĉi tio).", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Iuj provizantoj ne lasos vin agordi vian inversan DNS (aŭ ilia funkcio povus esti rompita ...). Se vi spertas problemojn pro tio, konsideru jenajn solvojn:
- Iuj ISP provizas la alternativon de uzante retpoŝtan servilon kvankam ĝi implicas, ke la relajso povos spioni vian retpoŝtan trafikon.
- Interreta privateco estas uzi VPN * kun dediĉita publika IP * por preterpasi ĉi tiajn limojn. Vidu
https://yunohost.org/#/vpn_avantage
- Finfine eblas ankaŭ ŝanĝo de provizanto ", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Iuj provizantoj ne lasos vin agordi vian inversan DNS (aŭ ilia funkcio povus esti rompita ...). Se vi spertas problemojn pro tio, konsideru jenajn solvojn:
- Iuj ISP provizas la alternativon de uzante retpoŝtan servilon kvankam ĝi implicas, ke la relajso povos spioni vian retpoŝtan trafikon.
- Interreta privateco estas uzi VPN * kun dediĉita publika IP * por preterpasi ĉi tiajn limojn. Vidu https://yunohost.org/#/vpn_avantage
- Finfine eblas ankaŭ ŝanĝo de provizanto", "diagnosis_display_tip": "Por vidi la trovitajn problemojn, vi povas iri al la sekcio pri Diagnozo de la reteja administrado, aŭ funkcii \"yunohost diagnosis show --issues\" el la komandlinio.", - "diagnosis_ip_global": "Tutmonda IP: {global} ", - "diagnosis_ip_local": "Loka IP: {local} ", - "diagnosis_dns_point_to_doc": "Bonvolu kontroli la dokumentaron ĉe https://yunohost.org/dns_config se vi bezonas helpon pri agordo de DNS-registroj.", + "diagnosis_ip_global": "Tutmonda IP: {global} ", + "diagnosis_ip_local": "Loka IP: {local} ", + "diagnosis_dns_point_to_doc": "Bonvolu kontroli la dokumentaron ĉe https://yunohost.org/dns_config se vi bezonas helpon pri agordo de DNS-registroj.", "diagnosis_mail_outgoing_port_25_ok": "La SMTP-poŝta servilo kapablas sendi retpoŝtojn (eliranta haveno 25 ne estas blokita).", "diagnosis_mail_outgoing_port_25_blocked_details": "Vi unue provu malŝlosi elirantan havenon 25 en via interreta enkursigilo aŭ en via retprovizanta interfaco. (Iuj gastigantaj provizantoj eble postulas, ke vi sendu al ili subtenan bileton por ĉi tio).", "diagnosis_mail_ehlo_unreachable": "La SMTP-poŝta servilo estas neatingebla de ekstere sur IPv {ipversion}. Ĝi ne povos ricevi retpoŝtojn.", "diagnosis_mail_ehlo_ok": "La SMTP-poŝta servilo atingeblas de ekstere kaj tial kapablas ricevi retpoŝtojn !", - "diagnosis_mail_ehlo_unreachable_details": "Ne povis malfermi rilaton sur la haveno 25 al via servilo en IPv {ipversion}. Ĝi ŝajnas esti neatingebla.
1. La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 25 ne estas ĝuste sendita al via servilo .
2. Vi ankaŭ devas certigi, ke servo-prefikso funkcias.
3. Pri pli kompleksaj agordoj: certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", + "diagnosis_mail_ehlo_unreachable_details": "Ne povis malfermi rilaton sur la haveno 25 al via servilo en IPv {ipversion}. Ĝi ŝajnas esti neatingebla.
1. La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 25 ne estas ĝuste sendita al via servilo .
2. Vi ankaŭ devas certigi, ke servo-prefikso funkcias.
3. Pri pli kompleksaj agordoj: certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", "diagnosis_mail_ehlo_bad_answer": "Ne-SMTP-servo respondita sur la haveno 25 sur IPv {ipversion}", "diagnosis_mail_ehlo_bad_answer_details": "Povas esti ke alia maŝino respondas anstataŭ via servilo.", "diagnosis_mail_ehlo_wrong": "Malsama SMTP-poŝta servilo respondas pri IPv {ipversion}. Via servilo probable ne povos ricevi retpoŝtojn.", - "diagnosis_mail_ehlo_wrong_details": "La EHLO ricevita de la fora diagnozilo en IPv {ipversion} diferencas de la domajno de via servilo.
Ricevita EHLO: {wrong_ehlo}
Atendita: {right_ehlo}
La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 25 ne estas ĝuste sendita al via servilo . Alternative, certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", + "diagnosis_mail_ehlo_wrong_details": "La EHLO ricevita de la fora diagnozilo en IPv {ipversion} diferencas de la domajno de via servilo.
Ricevita EHLO: {wrong_ehlo}
Atendita: {right_ehlo}
La plej ofta kaŭzo por ĉi tiu afero estas, ke la haveno 25 ne estas ĝuste sendita al via servilo . Alternative, certigu, ke neniu fajroŝirmilo aŭ reverso-prokuro ne interbatalas.", "diagnosis_mail_ehlo_could_not_diagnose": "Ne povis diagnozi ĉu postfiksa poŝta servilo atingebla de ekstere en IPv {ipversion}.", "diagnosis_mail_ehlo_could_not_diagnose_details": "Eraro: {error}", "diagnosis_mail_fcrdns_ok": "Via inversa DNS estas ĝuste agordita!", "diagnosis_mail_fcrdns_dns_missing": "Neniu inversa DNS estas difinita en IPv {ipversion}. Iuj retpoŝtoj povas malsukcesi liveri aŭ povus esti markitaj kiel spamo.", - "diagnosis_mail_fcrdns_nok_alternatives_6": "Iuj provizantoj ne lasos vin agordi vian inversan DNS (aŭ ilia funkcio povus esti rompita ...). Se via inversa DNS estas ĝuste agordita por IPv4, vi povas provi malebligi la uzon de IPv6 kiam vi sendas retpoŝtojn per funkciado yunohost-agordoj set smtp.allow_ipv6 -v off . Noto: ĉi tiu lasta solvo signifas, ke vi ne povos sendi aŭ ricevi retpoŝtojn de la malmultaj IPv6-nur serviloj tie.", + "diagnosis_mail_fcrdns_nok_alternatives_6": "Iuj provizantoj ne lasos vin agordi vian inversan DNS (aŭ ilia funkcio povus esti rompita ...). Se via inversa DNS estas ĝuste agordita por IPv4, vi povas provi malebligi la uzon de IPv6 kiam vi sendas retpoŝtojn per funkciado yunohost-agordoj set smtp.allow_ipv6 -v off . Noto: ĉi tiu lasta solvo signifas, ke vi ne povos sendi aŭ ricevi retpoŝtojn de la malmultaj IPv6-nur serviloj tie.", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "La inversa DNS ne ĝuste agordis en IPv {ipversion}. Iuj retpoŝtoj povas malsukcesi liveri aŭ povus esti markitaj kiel spamo.", - "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Aktuala reverso DNS: {rdns_domain}
Atendita valoro: {ehlo_domain}", + "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Aktuala reverso DNS: {rdns_domain}
Atendita valoro: {ehlo_domain}", "diagnosis_mail_blacklist_ok": "La IP kaj domajnoj uzataj de ĉi tiu servilo ne ŝajnas esti listigitaj nigre", "diagnosis_mail_blacklist_listed_by": "Via IP aŭ domajno {item} estas listigita en {blacklist_name}", "diagnosis_mail_blacklist_reason": "La negra listo estas: {reason}", From 72d4460bb4af04edff1a3a10a2b6e35bb33917e8 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 May 2020 20:39:50 +0200 Subject: [PATCH 092/482] Typo / wording / grammar ? --- locales/fr.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index bf5598f75..3f9c9ba8c 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -640,12 +640,12 @@ "diagnosis_http_nginx_conf_not_up_to_date_details": "Pour corriger la situation, inspectez la différence avec la ligne de commande en utilisant les outils yunohost regen-conf nginx --dry-run --with-diff et si vous êtes d’accord, appliquez les modifications avec yunohost tools regen-conf nginx --force.", "backup_archive_cant_retrieve_info_json": "Impossible d'avoir des informations sur l'archive '{archive}' ... Le fichier info.json ne peut pas être trouvé (ou n'est pas un fichier json valide).", "backup_archive_corrupted": "Il semble que l'archive de la sauvegarde '{archive}' est corrompue : {error}", - "diagnosis_ip_no_ipv6_tip": "L'utilisation de IPv6 n'est pas obligatoire pour le fonctionnement de votre serveur, mais cela permet un meilleur fonctionnement de l'internet dans son ensemble. IPv6 peut-être automatiquement configuré par votre système ou votre FAI s'il est disponible. Autrement, vous devrez prendre quelque minute pour le configurer manuellement comme il est écrit dans cette documentation: https://yunohost.org/#/ipv6. Si vous ne pouvez pas activer IPv6 ou si c'est trop technique pour vous, vous pouvez ignorer cet avertissement sans problème.", + "diagnosis_ip_no_ipv6_tip": "L'utilisation de IPv6 n'est pas obligatoire pour le fonctionnement de votre serveur, mais cela contribue à la santé d'Internet dans son ensemble. IPv6 généralement configuré automatiquement par votre système ou votre FAI s'il est disponible. Autrement, vous devrez prendre quelque minutes pour le configurer manuellement à l'aide de cette documentation: https://yunohost.org/#/ipv6. Si vous ne pouvez pas activer IPv6 ou si c'est trop technique pour vous, vous pouvez aussi ignorer cet avertissement sans que cela pose problème.", "diagnosis_domain_expiration_not_found": "Impossible de vérifier la date d'expiration de certains domaines", "diagnosis_domain_expiration_not_found_details": "Les informations WHOIS pour le domaine {domain} ne semblent pas contenir les informations concernant la date d'expiration ?", "diagnosis_domain_not_found_details": "Le domaine {domain} n'existe pas dans la base de donnée WHOIS ou est expiré !", - "diagnosis_domain_expiration_success": "Vos domaines sont enregistrés et ne sont pas expirés prochainement.", - "diagnosis_domain_expiration_warning": "Certains domaines vont expirés prochainement !", - "diagnosis_domain_expiration_error": "Certains domaines vont expirés TRÈS PROCHAINEMENT !", + "diagnosis_domain_expiration_success": "Vos domaines sont enregistrés et ne vont pas expirer prochainement.", + "diagnosis_domain_expiration_warning": "Certains domaines vont expirer prochainement !", + "diagnosis_domain_expiration_error": "Certains domaines vont expirer TRÈS PROCHAINEMENT !", "diagnosis_domain_expires_in": "Le {domain} expire dans {days} jours." } From f8154fe23ab6bcd2a373b0970e6f8a73a8484fbd Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 May 2020 21:21:50 +0200 Subject: [PATCH 093/482] Update changelog for 3.8.4 --- debian/changelog | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/debian/changelog b/debian/changelog index 40109eff9..ed5a87aea 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,21 @@ +yunohost (3.8.4) testing; urgency=low + + - [fix] Restoration of custom hooks / missing restore hooks (#927) + - [enh] Real CSP headers for the webadmin (#961) + - [enh] Simplify / optimize reading version of yunohost packages... (#968) + - [fix] handle new auto restart of ldap in moulinette (#975) + - [enh] service.py cleanup + add tests for services (#979, #986) + - [fix] Enforce permissions for stuff in /etc/yunohost/ (#963) + - [mod] Remove security diagnosis category for now, Move meltdown check to base system (a799740a) + - [mod] Change warning/errors about swap as info instead ... add a tip about the fact that having swap on SD or SSD is dangerous (23147161) + - [enh] Improve auto diagnosis cron UX, add a --human-readable option to diagnosis_show() (aecbb14a) + - [enh] Rely on new diagnosis for letsencrypt elligibility (#985) + - [i18n] Translations updated for Catalan, Esperanto, French, Spanish + + Thanks to all contributors <3 ! (amirale qt, autra, Bram, clecle226, I. Hernández, Kay0u, xaloc33) + + -- Alexandre Aubin Sat, 09 May 2020 21:20:00 +0200 + yunohost (3.8.3) testing; urgency=low - [fix] Remove dot in reverse DNS check From c346f5f1df39ba0359079fa1878b357e2e9fb3df Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 9 May 2020 22:08:49 +0200 Subject: [PATCH 094/482] This file sometimes has stupid \x00 inside ~.~ --- data/hooks/diagnosis/00-basesystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py index dbb0ccf08..ec802c870 100644 --- a/data/hooks/diagnosis/00-basesystem.py +++ b/data/hooks/diagnosis/00-basesystem.py @@ -34,7 +34,7 @@ class BaseSystemDiagnoser(Diagnoser): # Also possibly the board name if os.path.exists("/proc/device-tree/model"): - model = read_file('/proc/device-tree/model').strip() + model = read_file('/proc/device-tree/model').strip().replace('\x00', '') hardware["data"]["model"] = model hardware["details"] = ["diagnosis_basesystem_hardware_board"] From 43facfd5b5dda727cf716a70861d11a7bcb6e551 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 11 May 2020 00:21:25 +0200 Subject: [PATCH 095/482] Again here, list.remove(foo) fails if foo ain't in list :[ --- src/yunohost/service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 40a0fcc0b..cb40d03bc 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -418,7 +418,8 @@ def service_log(name, number=50): # Legacy stuff related to --log_type where we'll typically have the service # name in the log list but it's not an actual logfile. Nowadays journalctl # is automatically fetch as well as regular log files. - log_list.remove(name) + if name in log_list: + log_list.remove(name) result = {} From afbeb145b6081e180518af8e7670d3ef4e955fb5 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 11 May 2020 00:36:46 +0200 Subject: [PATCH 096/482] Make sure we have a list for log_list --- src/yunohost/service.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index cb40d03bc..fc6d6f951 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -415,6 +415,9 @@ def service_log(name, number=50): log_list = services[name].get('log', []) + if not isinstance(log_list, list): + log_list = [log_list] + # Legacy stuff related to --log_type where we'll typically have the service # name in the log list but it's not an actual logfile. Nowadays journalctl # is automatically fetch as well as regular log files. From b6631b4882b8d5ed883b33fda87a73d048c63274 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 11 May 2020 00:37:12 +0200 Subject: [PATCH 097/482] Add a test for service_log --- src/yunohost/tests/test_service.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/yunohost/tests/test_service.py b/src/yunohost/tests/test_service.py index d8660c1e5..e968ac0a7 100644 --- a/src/yunohost/tests/test_service.py +++ b/src/yunohost/tests/test_service.py @@ -1,8 +1,8 @@ import os -from conftest import message, raiseYunohostError +from conftest import raiseYunohostError -from yunohost.service import _get_services, _save_services, service_status, service_add, service_remove +from yunohost.service import _get_services, _save_services, service_status, service_add, service_remove, service_log def setup_function(function): @@ -42,6 +42,13 @@ def test_service_status_single(): assert status["status"] == "running" +def test_service_log(): + + logs = service_log("ssh") + assert "journalctl" in logs.keys() + assert "/var/log/auth.log" in logs.keys() + + def test_service_status_unknown_service(mocker): with raiseYunohostError(mocker, 'service_unknown'): From 2205515d352c716ceeaab594bb8812f7dee5ff83 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 11 May 2020 00:37:25 +0200 Subject: [PATCH 098/482] Add a dummy description to avoid warning --- src/yunohost/tests/test_service.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/yunohost/tests/test_service.py b/src/yunohost/tests/test_service.py index e968ac0a7..ffe3629c5 100644 --- a/src/yunohost/tests/test_service.py +++ b/src/yunohost/tests/test_service.py @@ -81,24 +81,23 @@ def test_service_remove_service_that_doesnt_exists(mocker): def test_service_update_to_add_properties(): - service_add("dummyservice", description="") + service_add("dummyservice", description="dummy") assert not _get_services()["dummyservice"].get("test_status") - service_add("dummyservice", description="", test_status="true") + service_add("dummyservice", description="dummy", test_status="true") assert _get_services()["dummyservice"].get("test_status") == "true" def test_service_update_to_change_properties(): - service_add("dummyservice", description="", test_status="false") + service_add("dummyservice", description="dummy", test_status="false") assert _get_services()["dummyservice"].get("test_status") == "false" - service_add("dummyservice", description="", test_status="true") + service_add("dummyservice", description="dummy", test_status="true") assert _get_services()["dummyservice"].get("test_status") == "true" def test_service_update_to_remove_properties(): - service_add("dummyservice", description="", test_status="false") + service_add("dummyservice", description="dummy", test_status="false") assert _get_services()["dummyservice"].get("test_status") == "false" - service_add("dummyservice", description="", test_status="") + service_add("dummyservice", description="dummy", test_status="") assert not _get_services()["dummyservice"].get("test_status") - From 429df8c43f938c29c6b368d2dac1ce9b1759af4f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 11 May 2020 00:43:58 +0200 Subject: [PATCH 099/482] Ugh smaller treshold because people have exactly 500MB ... --- data/hooks/diagnosis/50-systemresources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py index 50f69f9ed..682fb897f 100644 --- a/data/hooks/diagnosis/50-systemresources.py +++ b/data/hooks/diagnosis/50-systemresources.py @@ -47,7 +47,7 @@ class SystemResourcesDiagnoser(Diagnoser): if swap.total <= 1 * MB: item["status"] = "INFO" item["summary"] = "diagnosis_swap_none" - elif swap.total < 500 * MB: + elif swap.total < 450 * MB: item["status"] = "INFO" item["summary"] = "diagnosis_swap_notsomuch" else: From b0136bd1aa88c57d83e636119a470f0f92258fed Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 11 May 2020 00:51:01 +0200 Subject: [PATCH 100/482] Update changelog for 3.8.4.1 --- debian/changelog | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/debian/changelog b/debian/changelog index ed5a87aea..139d390a5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +yunohost (3.8.4.1) testing; urgency=low + + - [mod] Tweak diagnosis threshold for swap warning (429df8c4) + - [fix] Make sure we have a list for log_list + make sure item is in list before using .remove()... (afbeb145, 43facfd5) + - [fix] Sometimes tree-model has a weird \x00 which breaks yunopaste (c346f5f1) + + -- Alexandre Aubin Mon, 11 May 2020 00:50:34 +0200 + yunohost (3.8.4) testing; urgency=low - [fix] Restoration of custom hooks / missing restore hooks (#927) From 7ccd6e1348321491ebcb2c6afec7be1de395e926 Mon Sep 17 00:00:00 2001 From: Julien Rabier Date: Mon, 11 May 2020 21:37:17 +0000 Subject: [PATCH 101/482] fix destination concurrency Hi, Postfix has this very peculiar behavior where the target of some config keys changes depending on the value. Here, if `smtp_destination_concurrency_limit` is set to 1, then according to http://www.postfix.org/postconf.5.html#default_destination_concurrency_limit it doesn't mean "1 concurrent mail per domain, but per recipiend address". So, if set to 1, it means we can send any volume of e-mails concurrently (with a 5s delay) if all recipient addresses are different. In order to avoid this, we should increase the value to restore the expected behavior (concurrency per domain, not per recipient). --- data/templates/postfix/main.cf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/templates/postfix/main.cf b/data/templates/postfix/main.cf index 61cbfa2e6..18e457a76 100644 --- a/data/templates/postfix/main.cf +++ b/data/templates/postfix/main.cf @@ -170,7 +170,7 @@ smtpd_milters = inet:localhost:11332 milter_default_action = accept # Avoid to send simultaneously too many emails -smtp_destination_concurrency_limit = 1 +smtp_destination_concurrency_limit = 2 default_destination_rate_delay = 5s # Avoid email adress scanning From 26fcfed7fb509828009ba5075e43fddb083818ad Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 May 2020 15:20:49 +0200 Subject: [PATCH 102/482] Only mention packages that couldn't be upgraded during failed apt upgrades --- src/yunohost/tools.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index abfc3b7af..790857f08 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -598,6 +598,8 @@ def tools_upgrade(operation_logger, apps=None, system=False): ) returncode = call_async_output(dist_upgrade, callbacks, shell=True) if returncode != 0: + upgradables = list(_list_upgradable_apt_packages()) + noncritical_packages_upgradable = [p["name"] for p in upgradables if p["name"] not in critical_packages] logger.warning(m18n.n('tools_upgrade_regular_packages_failed', packages_list=', '.join(noncritical_packages_upgradable))) operation_logger.error(m18n.n('packages_upgrade_failed')) From 4d734a27a0cc3f4c3bcf74df82c44435a83d63b7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 May 2020 16:18:23 +0200 Subject: [PATCH 103/482] Forcing unicode creates issue with non-ascii strings or whatever.. --- src/yunohost/app.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 950d0b401..640556b68 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -550,7 +550,7 @@ def app_upgrade(app=[], url=None, file=None): # Something wrong happened in Yunohost's code (most probably hook_exec) except Exception: import traceback - error = m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()) + error = m18n.n('unexpected_error', error="\n" + traceback.format_exc()) logger.error(m18n.n("app_install_failed", app=app_instance_name, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) finally: @@ -805,7 +805,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu # Something wrong happened in Yunohost's code (most probably hook_exec) except Exception as e: import traceback - error = m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()) + error = m18n.n('unexpected_error', error="\n" + traceback.format_exc()) logger.error(m18n.n("app_install_failed", app=app_id, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) finally: @@ -853,7 +853,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu except (KeyboardInterrupt, EOFError, Exception): remove_retcode = -1 import traceback - logger.error(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + logger.error(m18n.n('unexpected_error', error="\n" + traceback.format_exc())) # Remove all permission in LDAP for permission_name in user_permission_list()["permissions"].keys(): @@ -1042,7 +1042,7 @@ def app_remove(operation_logger, app): except (KeyboardInterrupt, EOFError, Exception): ret = -1 import traceback - logger.error(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())) + logger.error(m18n.n('unexpected_error', error="\n" + traceback.format_exc())) if ret == 0: logger.success(m18n.n('app_removed', app=app)) From 09d8500fda26268610da5e6ce206fbfeb50c8061 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 May 2020 16:38:27 +0200 Subject: [PATCH 104/482] Also run dpkg --audit to check if dpkg is in a broken state --- 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 51e9ab71a..6e6a922f6 100644 --- a/src/yunohost/utils/packages.py +++ b/src/yunohost/utils/packages.py @@ -95,6 +95,8 @@ def ynh_packages_version(*args, **kwargs): def dpkg_is_broken(): + if check_output("dpkg --audit").strip() != "": + return True # 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 From c6f184960c06b64a7cf0a44ac521a15931b45235 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 13 May 2020 16:59:59 +0200 Subject: [PATCH 105/482] We don't need to display hostname when fetching logs with journalctl --- 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 fc6d6f951..6a05c4d12 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -706,7 +706,7 @@ def _get_journalctl_logs(service, number="all"): services = _get_services() systemd_service = services.get(service, {}).get("actual_systemd_service", service) try: - return subprocess.check_output("journalctl -xn -u {0} -n{1}".format(systemd_service, number), shell=True) + return subprocess.check_output("journalctl --no-hostname -xn -u {0} -n{1}".format(systemd_service, number), shell=True) except: import traceback return "error while get services logs from journalctl:\n%s" % traceback.format_exc() From e67dc79197e5baf68b758b7bf9e7522a1b63381b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 May 2020 01:47:34 +0200 Subject: [PATCH 106/482] Add the damn short hostname to /etc/hosts automagically --- data/hooks/conf_regen/43-dnsmasq | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index 8a2985f34..c28d65288 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -64,6 +64,11 @@ do_post_regen() { systemctl restart resolvconf fi + # Some stupid things like rabbitmq-server used by onlyoffice won't work if + # the *short* hostname doesn't exists in /etc/hosts -_- + short_hostname=$(hostname -s) + grep -q "127.0.0.1.*$short_hostname" /etc/hosts || echo -e "127.0.0.1\t$short_hostname" >>/etc/hosts + [[ -z "$regen_conf_files" ]] \ || service dnsmasq restart } From 97199d19619804e0a330f781132bbe58e4bd6544 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 May 2020 03:25:24 +0200 Subject: [PATCH 107/482] Sometimes dpkg --configure -a ain't enough... --- locales/en.json | 4 ++-- locales/fr.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/en.json b/locales/en.json index 6f2590e2f..25e5a500a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -274,7 +274,7 @@ "domains_available": "Available domains:", "done": "Done", "downloading": "Downloading…", - "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`.", + "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 apt install --fix-broken` and/or `sudo dpkg --configure -a`.", "dpkg_lock_not_available": "This command can't be run right now because another program seems to be using the lock of dpkg (the system package manager)", "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}.", @@ -597,7 +597,7 @@ "ssowat_conf_updated": "SSOwat configuration updated", "system_upgraded": "System upgraded", "system_username_exists": "Username already exists in the list of 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`.", + "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 apt install --fix-broken` and/or `sudo dpkg --configure -a`.", "tools_upgrade_at_least_one": "Please specify '--apps', or '--system'", "tools_upgrade_cant_both": "Cannot upgrade both system and apps at the same time", "tools_upgrade_cant_hold_critical_packages": "Could not hold critical packages…", diff --git a/locales/fr.json b/locales/fr.json index 3f9c9ba8c..96d815b1a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -364,7 +364,7 @@ "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": "DANGER ! Cette application est connue pour être encore expérimentale (si elle ne fonctionne pas explicitement) ! Vous ne devriez probablement PAS l’installer à moins de savoir ce que vous faites. AUCUN SUPPORT ne sera fourni si cette application ne fonctionne pas ou casse votre système … Si vous êtes prêt à prendre ce risque de toute façon, tapez '{answers:s}'", "confirm_app_install_thirdparty": "DANGER! Cette application ne fait pas partie du catalogue d'applications de Yunohost. 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 à moins de savoir ce que vous faites. AUCUN SUPPORT ne sera fourni si cette application ne fonctionne pas ou casse votre système ... Si vous êtes prêt à prendre ce risque de toute façon, tapez '{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'.", + "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 apt install --fix-broken` et/ou `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", @@ -390,7 +390,7 @@ "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 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 apt install --fix-broken` et/ou `sudo dpkg --configure -a`.", "app_action_cannot_be_ran_because_required_services_down": "Ces services requis doivent être en cours d’exécution pour exécuter cette action : {services}. Essayez de les redémarrer pour continuer (et éventuellement rechercher pourquoi ils sont en panne).", "admin_password_too_long": "Veuillez choisir un mot de passe de moins de 127 caractères", "log_regen_conf": "Régénérer les configurations du système '{}'", From 65c87d55df2c0c12ed0effa3c6351d943dd5ea0c Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 May 2020 03:56:32 +0200 Subject: [PATCH 108/482] Try to not have weird warnings if no diagnosis ran yet... --- src/yunohost/certificate.py | 19 +++++++++---------- src/yunohost/diagnosis.py | 5 +++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/yunohost/certificate.py b/src/yunohost/certificate.py index 366f45462..4b5adb754 100644 --- a/src/yunohost/certificate.py +++ b/src/yunohost/certificate.py @@ -103,10 +103,16 @@ def certificate_status(domain_list, full=False): if not full: del status["subject"] del status["CA_name"] - del status["ACME_eligible"] status["CA_type"] = status["CA_type"]["verbose"] status["summary"] = status["summary"]["verbose"] + if full: + try: + _check_domain_is_ready_for_ACME(domain) + status["ACME_eligible"] = True + except: + status["ACME_eligible"] = False + del status["domain"] certificates[domain] = status @@ -700,12 +706,6 @@ def _get_status(domain): "verbose": "Unknown?", } - try: - _check_domain_is_ready_for_ACME(domain) - ACME_eligible = True - except: - ACME_eligible = False - return { "domain": domain, "subject": cert_subject, @@ -713,7 +713,6 @@ def _get_status(domain): "CA_type": CA_type, "validity": days_remaining, "summary": status_summary, - "ACME_eligible": ACME_eligible } # @@ -791,8 +790,8 @@ def _backup_current_cert(domain): def _check_domain_is_ready_for_ACME(domain): - dnsrecords = Diagnoser.get_cached_report("dnsrecords", item={"domain": domain, "category": "basic"}) or {} - httpreachable = Diagnoser.get_cached_report("web", item={"domain": domain}) or {} + dnsrecords = Diagnoser.get_cached_report("dnsrecords", item={"domain": domain, "category": "basic"}, warn_if_no_cache=False) or {} + httpreachable = Diagnoser.get_cached_report("web", item={"domain": domain}, warn_if_no_cache=False) or {} if not dnsrecords or not httpreachable: raise YunohostError('certmanager_domain_not_diagnosed_yet', domain=domain) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 806285f52..3f34f206e 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -427,10 +427,11 @@ class Diagnoser(): return os.path.join(DIAGNOSIS_CACHE, "%s.json" % id_) @staticmethod - def get_cached_report(id_, item=None): + def get_cached_report(id_, item=None, warn_if_no_cache=True): cache_file = Diagnoser.cache_file(id_) if not os.path.exists(cache_file): - logger.warning(m18n.n("diagnosis_no_cache", category=id_)) + if warn_if_no_cache: + logger.warning(m18n.n("diagnosis_no_cache", category=id_)) report = {"id": id_, "cached_for": -1, "timestamp": -1, From 9cbd368dca61d9ff9d707ead477d7ceadd271f51 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 May 2020 04:46:18 +0200 Subject: [PATCH 109/482] Tweak apt/dpkg options to avoid the shitload of lines about progress bar stuff in logs --- data/helpers.d/apt | 2 +- src/yunohost/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 3b4b199d0..dcea0c976 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -96,7 +96,7 @@ ynh_package_version() { # Requires YunoHost version 2.4.0.3 or higher. ynh_apt() { ynh_wait_dpkg_free - LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get --assume-yes $@ + LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get --assume-yes --quiet -o=Dpkg::Use-Pty=0 $@ } # Update package index files diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 790857f08..0e9d23e87 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -564,7 +564,7 @@ def tools_upgrade(operation_logger, apps=None, system=False): dist_upgrade = "DEBIAN_FRONTEND=noninteractive" dist_upgrade += " APT_LISTCHANGES_FRONTEND=none" dist_upgrade += " apt-get" - dist_upgrade += " --fix-broken --show-upgraded --assume-yes" + dist_upgrade += " --fix-broken --show-upgraded --assume-yes --quiet -o=Dpkg::Use-Pty=0" for conf_flag in ["old", "miss", "def"]: dist_upgrade += ' -o Dpkg::Options::="--force-conf{}"'.format(conf_flag) dist_upgrade += " dist-upgrade" From e140546092da3d6fd1e1220e2573f07df46a5865 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 May 2020 19:13:08 +0200 Subject: [PATCH 110/482] Hmgn need to make sure to write this on a new line --- data/hooks/conf_regen/43-dnsmasq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq index c28d65288..8cddec1be 100755 --- a/data/hooks/conf_regen/43-dnsmasq +++ b/data/hooks/conf_regen/43-dnsmasq @@ -67,7 +67,7 @@ do_post_regen() { # Some stupid things like rabbitmq-server used by onlyoffice won't work if # the *short* hostname doesn't exists in /etc/hosts -_- short_hostname=$(hostname -s) - grep -q "127.0.0.1.*$short_hostname" /etc/hosts || echo -e "127.0.0.1\t$short_hostname" >>/etc/hosts + grep -q "127.0.0.1.*$short_hostname" /etc/hosts || echo -e "\n127.0.0.1\t$short_hostname" >>/etc/hosts [[ -z "$regen_conf_files" ]] \ || service dnsmasq restart From 4cd4938eb4dfdc7b204444d4903dce957ad23d8f Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 14 May 2020 19:32:45 +0200 Subject: [PATCH 111/482] Change logic of --email to avoid sending empty mail is some issues are found but ignored --- src/yunohost/diagnosis.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/yunohost/diagnosis.py b/src/yunohost/diagnosis.py index 3f34f206e..4fad86ffd 100644 --- a/src/yunohost/diagnosis.py +++ b/src/yunohost/diagnosis.py @@ -183,11 +183,10 @@ def diagnosis_run(categories=[], force=False, except_if_never_ran_yet=False, ema if report != {}: issues.extend([item for item in report["items"] if item["status"] in ["WARNING", "ERROR"]]) - if issues: - if email: - _email_diagnosis_issues() - elif msettings.get("interface") == "cli": - logger.warning(m18n.n("diagnosis_display_tip")) + if email: + _email_diagnosis_issues() + if issues and msettings.get("interface") == "cli": + logger.warning(m18n.n("diagnosis_display_tip")) def diagnosis_ignore(add_filter=None, remove_filter=None, list=False): @@ -565,7 +564,11 @@ def _email_diagnosis_issues(): disclaimer = "The automatic diagnosis on your YunoHost server identified some issues on your server. You will find a description of the issues below. You can manage those issues in the 'Diagnosis' section in your webadmin." - content = _dump_human_readable_reports(diagnosis_show(issues=True)["reports"]) + issues = diagnosis_show(issues=True)["reports"] + if not issues: + return + + content = _dump_human_readable_reports(issues) message = """\ From: %s @@ -579,9 +582,6 @@ Subject: %s %s """ % (from_, to_, subject_, disclaimer, content) - print(message) - smtp = smtplib.SMTP("localhost") smtp.sendmail(from_, [to_], message) smtp.quit() - From 757cef32b33f749e3bceba060ef4a25b4392bdd8 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 14 May 2020 21:30:00 +0200 Subject: [PATCH 112/482] [mod] remove unused import --- 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 640556b68..c8e37d787 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2389,7 +2389,7 @@ def _parse_args_in_yunohost_format(args, action_args): """Parse arguments store in either manifest.json or actions.json """ from yunohost.domain import domain_list, _get_maindomain - from yunohost.user import user_info, user_list + from yunohost.user import user_list args_dict = OrderedDict() From c600b3b53e0ef45aa6fc8e546964f684b8d3d4de Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 15 May 2020 03:23:12 +0200 Subject: [PATCH 113/482] [mod] rename everything in _parse_args_in_yunohost_format because I'm too old and too tired for shitty variable name, also docstring --- src/yunohost/app.py | 142 +++++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 67 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c8e37d787..300fbcc81 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2385,126 +2385,134 @@ def _parse_args_for_action(action, args={}): return _parse_args_in_yunohost_format(args, action_args) -def _parse_args_in_yunohost_format(args, action_args): - """Parse arguments store in either manifest.json or actions.json +def _parse_args_in_yunohost_format(user_answers, argument_questions): + """Parse arguments store in either manifest.json or actions.json or from a + config panel against the user answers when they are present. + + Keyword arguments: + user_answers -- a dictionnary of arguments from the user (generally + empty in CLI, filed from the admin interface) + argument_questions -- the arguments description store in yunohost + format from actions.json/toml, manifest.json/toml + or config_panel.json/toml """ from yunohost.domain import domain_list, _get_maindomain from yunohost.user import user_list - args_dict = OrderedDict() + parsed_answers_dict = OrderedDict() - for arg in action_args: - arg_name = arg['name'] - arg_type = arg.get('type', 'string') - arg_default = arg.get('default', None) - arg_choices = arg.get('choices', []) - arg_value = None + for question in argument_questions: + question_name = question['name'] + question_type = question.get('type', 'string') + question_default = question.get('default', None) + question_choices = question.get('choices', []) + question_value = None # Transpose default value for boolean type and set it to # false if not defined. - if arg_type == 'boolean': - arg_default = 1 if arg_default else 0 + if question_type == 'boolean': + question_default = 1 if question_default else 0 # do not print for webadmin - if arg_type == 'display_text' and msettings.get('interface') != 'api': - print(_value_for_locale(arg['ask'])) + if question_type == 'display_text' and msettings.get('interface') != 'api': + print(_value_for_locale(question['ask'])) continue # Attempt to retrieve argument value - if arg_name in args: - arg_value = args[arg_name] + if question_name in user_answers: + question_value = user_answers[question_name] else: - if 'ask' in arg: + if 'ask' in question: # Retrieve proper ask string - ask_string = _value_for_locale(arg['ask']) + text_for_user_input_in_cli = _value_for_locale(question['ask']) # Append extra strings - if arg_type == 'boolean': - ask_string += ' [yes | no]' - elif arg_choices: - ask_string += ' [{0}]'.format(' | '.join(arg_choices)) + if question_type == 'boolean': + text_for_user_input_in_cli += ' [yes | no]' + elif question_choices: + text_for_user_input_in_cli += ' [{0}]'.format(' | '.join(question_choices)) - if arg_default is not None: - if arg_type == 'boolean': - ask_string += ' (default: {0})'.format("yes" if arg_default == 1 else "no") + if question_default is not None: + if question_type == 'boolean': + text_for_user_input_in_cli += ' (default: {0})'.format("yes" if question_default == 1 else "no") else: - ask_string += ' (default: {0})'.format(arg_default) + text_for_user_input_in_cli += ' (default: {0})'.format(question_default) # Check for a password argument - is_password = True if arg_type == 'password' else False + is_password = True if question_type == 'password' else False - if arg_type == 'domain': - arg_default = _get_maindomain() - ask_string += ' (default: {0})'.format(arg_default) + if question_type == 'domain': + question_default = _get_maindomain() + text_for_user_input_in_cli += ' (default: {0})'.format(question_default) msignals.display(m18n.n('domains_available')) for domain in domain_list()['domains']: msignals.display("- {}".format(domain)) - elif arg_type == 'user': + elif question_type == 'user': msignals.display(m18n.n('users_available')) for user in user_list()['users'].keys(): msignals.display("- {}".format(user)) - elif arg_type == 'password': + elif question_type == 'password': msignals.display(m18n.n('good_practices_about_user_password')) try: - input_string = msignals.prompt(ask_string, is_password) + input_string = msignals.prompt(text_for_user_input_in_cli, is_password) except NotImplementedError: input_string = None if (input_string == '' or input_string is None) \ - and arg_default is not None: - arg_value = arg_default + and question_default is not None: + question_value = question_default else: - arg_value = input_string - elif arg_default is not None: - arg_value = arg_default + question_value = input_string + elif question_default is not None: + question_value = question_default # 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): + # then check if question is optional or not + if question_value is None or question_value == '': + if question.get("optional", False): # Argument is optional, keep an empty value - # and that's all for this arg ! - args_dict[arg_name] = ('', arg_type) + # and that's all for this question! + parsed_answers_dict[question_name] = ('', question_type) continue else: # The argument is required ! - raise YunohostError('app_argument_required', name=arg_name) + raise YunohostError('app_argument_required', name=question_name) # Validate argument choice - if arg_choices and arg_value not in arg_choices: - raise YunohostError('app_argument_choice_invalid', name=arg_name, choices=', '.join(arg_choices)) + if question_choices and question_value not in question_choices: + raise YunohostError('app_argument_choice_invalid', name=question_name, choices=', '.join(question_choices)) # Validate argument type - if arg_type == 'domain': - if arg_value not in domain_list()['domains']: - raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('domain_unknown')) - elif arg_type == 'user': - if not arg_value in user_list()["users"].keys(): - raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('user_unknown', user=arg_value)) - elif arg_type == 'app': - if not _is_installed(arg_value): - raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('app_unknown')) - elif arg_type == 'boolean': - if isinstance(arg_value, bool): - arg_value = 1 if arg_value else 0 + if question_type == 'domain': + if question_value not in domain_list()['domains']: + raise YunohostError('app_argument_invalid', name=question_name, error=m18n.n('domain_unknown')) + elif question_type == 'user': + if question_value not in user_list()["users"].keys(): + raise YunohostError('app_argument_invalid', name=question_name, error=m18n.n('user_unknown', user=question_value)) + elif question_type == 'app': + if not _is_installed(question_value): + raise YunohostError('app_argument_invalid', name=question_name, error=m18n.n('app_unknown')) + elif question_type == 'boolean': + if isinstance(question_value, bool): + question_value = 1 if question_value else 0 else: - if str(arg_value).lower() in ["1", "yes", "y"]: - arg_value = 1 - elif str(arg_value).lower() in ["0", "no", "n"]: - arg_value = 0 + if str(question_value).lower() in ["1", "yes", "y"]: + question_value = 1 + elif str(question_value).lower() in ["0", "no", "n"]: + question_value = 0 else: - raise YunohostError('app_argument_choice_invalid', name=arg_name, choices='yes, no, y, n, 1, 0') - elif arg_type == 'password': + raise YunohostError('app_argument_choice_invalid', name=question_name, choices='yes, no, y, n, 1, 0') + elif question_type == 'password': forbidden_chars = "{}" - if any(char in arg_value for char in forbidden_chars): + if any(char in question_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, arg_type) + assert_password_is_strong_enough('user', question_value) + parsed_answers_dict[question_name] = (question_value, question_type) - return args_dict + return parsed_answers_dict def _validate_and_normalize_webpath(manifest, args_dict, app_folder): From 5850bf610fcf7a4e928eed56db55d12b58f8f5b4 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 15 May 2020 04:00:58 +0200 Subject: [PATCH 114/482] Get rid of those damn warnings about file descriptors --- src/yunohost/hook.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/yunohost/hook.py b/src/yunohost/hook.py index 40d3d114f..dbfd7eceb 100644 --- a/src/yunohost/hook.py +++ b/src/yunohost/hook.py @@ -323,8 +323,8 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, # Define output loggers and call command loggers = ( - lambda l: logger.debug(l.rstrip()+"\r"), - lambda l: logger.warning(l.rstrip()), + lambda l: logger.debug(l.rstrip() + "\r"), + lambda l: logger.warning(l.rstrip()) if "invalid value for trace file descriptor" not in l.rstrip() else logger.debug(l.rstrip()), lambda l: logger.info(l.rstrip()) ) From 413778d2bce74d51e4274cff4eecdb4713d60e19 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 15 May 2020 04:23:58 +0200 Subject: [PATCH 115/482] Check if app broke something important only if install succeeded (if install fails, this check only matters *after* we remove the app which is already done) --- src/yunohost/app.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c8e37d787..a2ab5a25e 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -809,15 +809,15 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu logger.error(m18n.n("app_install_failed", app=app_id, error=error)) failure_message_with_debug_instructions = operation_logger.error(error) finally: - # Whatever happened (install success or failure) we check if it broke the system - # and warn the user about it - try: - broke_the_system = False - _assert_system_is_sane_for_app(manifest, "post") - except Exception as e: - broke_the_system = True - logger.error(m18n.n("app_install_failed", app=app_id, error=str(e))) - failure_message_with_debug_instructions = operation_logger.error(str(e)) + # If success so far, validate that app didn't break important stuff + if not install_failed: + try: + broke_the_system = False + _assert_system_is_sane_for_app(manifest, "post") + except Exception as e: + broke_the_system = True + logger.error(m18n.n("app_install_failed", app=app_id, error=str(e))) + failure_message_with_debug_instructions = operation_logger.error(str(e)) # If the install failed or broke the system, we remove it if install_failed or broke_the_system: From fd358fdfcc38c35d7c2e09161e36b16c746125c9 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Fri, 15 May 2020 05:21:36 +0200 Subject: [PATCH 116/482] [enh] start writting test for arguments parsing --- .../tests/test_apps_arguments_parsing.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/yunohost/tests/test_apps_arguments_parsing.py diff --git a/src/yunohost/tests/test_apps_arguments_parsing.py b/src/yunohost/tests/test_apps_arguments_parsing.py new file mode 100644 index 000000000..8fe3d7728 --- /dev/null +++ b/src/yunohost/tests/test_apps_arguments_parsing.py @@ -0,0 +1,109 @@ +import pytest +from collections import OrderedDict +from mock import patch + +from moulinette import msignals + +from yunohost.app import _parse_args_in_yunohost_format +from yunohost.utils.error import YunohostError + + +""" +Argument default format: +{ + "name": "the_name", + "type": "one_of_the_available_type", // "sting" is not specified + "ask": { + "en": "the question in english", + "fr": "the question in french" + }, + "help": { + "en": "some help text in english", + "fr": "some help text in french" + }, + "example": "an example value", // optional + "default", "some stuff", // optional, not available for all types + "optional": true // optional, will skip if not answered +} + +User answers: +{"name": "value", ...} +""" + + +def test_parse_args_in_yunohost_format_empty(): + assert _parse_args_in_yunohost_format({}, []) == {} + + +def test_parse_args_in_yunohost_format_string(): + questions = [{ + "name": "some_string", + "type": "string", + }] + answers = {"some_string": "some_value"} + expected_result = OrderedDict({"some_string": ("some_value", "string")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_string_default_type(): + questions = [{ + "name": "some_string", + }] + answers = {"some_string": "some_value"} + expected_result = OrderedDict({"some_string": ("some_value", "string")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_string_no_input(): + questions = [{ + "name": "some_string", + }] + answers = {} + + with pytest.raises(YunohostError): + _parse_args_in_yunohost_format(answers, questions) + + +def test_parse_args_in_yunohost_format_string_input(): + questions = [{ + "name": "some_string", + "ask": "some question", + }] + answers = {} + expected_result = OrderedDict({"some_string": ("some_value", "string")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +@pytest.mark.skip # that shit should work x( +def test_parse_args_in_yunohost_format_string_input_no_ask(): + questions = [{ + "name": "some_string", + }] + answers = {} + expected_result = OrderedDict({"some_string": ("some_value", "string")}) + + with patch.object(msignals, "prompt", return_value="some_value"): + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_string_no_input_optional(): + questions = [{ + "name": "some_string", + "optional": True, + }] + answers = {} + expected_result = OrderedDict({"some_string": ("", "string")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result + + +def test_parse_args_in_yunohost_format_string_no_input_default(): + questions = [{ + "name": "some_string", + "ask": "some question", + "default": "some_value", + }] + answers = {} + expected_result = OrderedDict({"some_string": ("some_value", "string")}) + assert _parse_args_in_yunohost_format(answers, questions) == expected_result From c9b2213817a9da286f9014672d229f14ea83fc5a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 15 May 2020 17:06:24 +0200 Subject: [PATCH 117/482] Don't miserably crash if doveadm fails to run --- src/yunohost/user.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/yunohost/user.py b/src/yunohost/user.py index 282ec8407..7f8f2dc35 100644 --- a/src/yunohost/user.py +++ b/src/yunohost/user.py @@ -467,9 +467,14 @@ def user_info(username): elif username not in user_permission_list(full=True)["permissions"]["mail.main"]["corresponding_users"]: 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, - shell=True) + try: + cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0] + cmd_result = subprocess.check_output(cmd, stderr=subprocess.STDOUT, + shell=True) + except Exception as e: + cmd_result = "" + logger.warning("Failed to fetch quota info ... : %s " % str(e)) + # Exemple of return value for cmd: # """Quota name=User quota Type=STORAGE Value=0 Limit=- %=0 # Quota name=User quota Type=MESSAGE Value=0 Limit=- %=0""" From dd09758fb55eaff4e3cc79dc413a837f549d132b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 15 May 2020 23:40:41 +0200 Subject: [PATCH 118/482] Report the service status as unknown if service type is oneshot and status exited --- src/yunohost/service.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 6a05c4d12..d236de020 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -85,7 +85,7 @@ def service_add(name, description=None, log=None, log_type=None, test_status=Non # systemd will anyway return foo.service as default value, so we wanna # make sure there's actually something here. if out == name + ".service": - logger.warning("/!\\ Packager ! You added a custom service without specifying a description. Please add a proper Description in the systemd configuration, or use --description to explain what the service does in a similar fashion to existing services.") + logger.warning("/!\\ Packagers! You added a custom service without specifying a description. Please add a proper Description in the systemd configuration, or use --description to explain what the service does in a similar fashion to existing services.") else: service['description'] = out @@ -94,6 +94,8 @@ def service_add(name, description=None, log=None, log_type=None, test_status=Non if test_status: service["test_status"] = test_status + elif subprocess.check_output("systemctl show %s | grep '^Type='" % name, shell=True).strip() == "oneshot": + logger.warning("/!\\ Packagers! Please provide a --test_status when adding oneshot-type services in Yunohost, such that it has a reliable way to check if the service is running or not.") if test_conf: service["test_conf"] = test_conf @@ -300,9 +302,9 @@ def service_status(names=[]): continue systemd_service = infos.get("actual_systemd_service", name) - status = _get_service_information_from_systemd(systemd_service) + raw_status, raw_service = _get_service_information_from_systemd(systemd_service) - if status is None: + if raw_status is None: logger.error("Failed to get status information via dbus for service %s, systemctl didn't recognize this service ('NoSuchUnit')." % systemd_service) result[name] = { 'status': "unknown", @@ -322,11 +324,11 @@ def service_status(names=[]): # that's the only way to test for that for now # if we don't have it, uses the one provided by systemd if description == translation_key: - description = str(status.get("Description", "")) + description = str(raw_status.get("Description", "")) result[name] = { - 'status': str(status.get("SubState", "unknown")), - 'start_on_boot': str(status.get("UnitFileState", "unknown")), + 'status': str(raw_status.get("SubState", "unknown")), + 'start_on_boot': str(raw_status.get("UnitFileState", "unknown")), 'last_state_change': "unknown", 'description': description, 'configuration': "unknown", @@ -339,8 +341,8 @@ def service_status(names=[]): elif os.path.exists("/etc/systemd/system/multi-user.target.wants/%s.service" % name): result[name]["start_on_boot"] = "enabled" - if "StateChangeTimestamp" in status: - result[name]['last_state_change'] = datetime.utcfromtimestamp(status["StateChangeTimestamp"] / 1000000) + if "StateChangeTimestamp" in raw_status: + result[name]['last_state_change'] = datetime.utcfromtimestamp(raw_status["StateChangeTimestamp"] / 1000000) # 'test_status' is an optional field to test the status of the service using a custom command if "test_status" in infos: @@ -353,6 +355,12 @@ def service_status(names=[]): p.communicate() result[name]["status"] = "running" if p.returncode == 0 else "failed" + elif raw_service.get("Type", "").lower() == "oneshot" and result[name]["status"] == "exited": + # These are services like yunohost-firewall, hotspot, vpnclient, + # ... they will be "exited" why doesn't provide any info about + # the real state of the service (unless they did provide a + # test_status, c.f. previous condition) + result[name]["status"] = "unknown" # 'test_status' is an optional field to test the status of the service using a custom command if "test_conf" in infos: @@ -389,13 +397,14 @@ def _get_service_information_from_systemd(service): service_proxy = d.get_object('org.freedesktop.systemd1', str(service_unit)) properties_interface = dbus.Interface(service_proxy, 'org.freedesktop.DBus.Properties') - properties = properties_interface.GetAll('org.freedesktop.systemd1.Unit') + unit = properties_interface.GetAll('org.freedesktop.systemd1.Unit') + service = properties_interface.GetAll('org.freedesktop.systemd1.Service') - if properties.get("LoadState", "not-found") == "not-found": + if unit.get("LoadState", "not-found") == "not-found": # Service doesn't really exist - return None + return (None, None) else: - return properties + return (unit, service) def service_log(name, number=50): From 1244241b3fa36b6041c901f70aef4c7d14d7ae57 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Fri, 15 May 2020 23:56:51 +0200 Subject: [PATCH 119/482] Have an independant function for building the service status --- src/yunohost/service.py | 156 +++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 75 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index d236de020..ddf34c9d0 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -301,81 +301,7 @@ def service_status(names=[]): if infos.get("status", "") is None: continue - systemd_service = infos.get("actual_systemd_service", name) - raw_status, raw_service = _get_service_information_from_systemd(systemd_service) - - if raw_status is None: - logger.error("Failed to get status information via dbus for service %s, systemctl didn't recognize this service ('NoSuchUnit')." % systemd_service) - result[name] = { - 'status': "unknown", - 'start_on_boot': "unknown", - 'last_state_change': "unknown", - 'description': "Error: failed to get information for this service, it doesn't exists for systemd", - 'configuration': "unknown", - } - - else: - translation_key = "service_description_%s" % name - description = infos.get("description") - if not description: - description = m18n.n(translation_key) - - # that mean that we don't have a translation for this string - # that's the only way to test for that for now - # if we don't have it, uses the one provided by systemd - if description == translation_key: - description = str(raw_status.get("Description", "")) - - result[name] = { - 'status': str(raw_status.get("SubState", "unknown")), - 'start_on_boot': str(raw_status.get("UnitFileState", "unknown")), - 'last_state_change': "unknown", - 'description': description, - 'configuration': "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]["start_on_boot"] == "generated": - result[name]["start_on_boot"] = "enabled" if glob("/etc/rc[S5].d/S??" + name) else "disabled" - elif os.path.exists("/etc/systemd/system/multi-user.target.wants/%s.service" % name): - result[name]["start_on_boot"] = "enabled" - - if "StateChangeTimestamp" in raw_status: - result[name]['last_state_change'] = datetime.utcfromtimestamp(raw_status["StateChangeTimestamp"] / 1000000) - - # 'test_status' is an optional field to test the status of the service using a custom command - if "test_status" in infos: - p = subprocess.Popen(infos["test_status"], - shell=True, - executable='/bin/bash', - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - - p.communicate() - - result[name]["status"] = "running" if p.returncode == 0 else "failed" - elif raw_service.get("Type", "").lower() == "oneshot" and result[name]["status"] == "exited": - # These are services like yunohost-firewall, hotspot, vpnclient, - # ... they will be "exited" why doesn't provide any info about - # the real state of the service (unless they did provide a - # test_status, c.f. previous condition) - result[name]["status"] = "unknown" - - # 'test_status' is an optional field to test the status of the service using a custom command - if "test_conf" in infos: - p = subprocess.Popen(infos["test_conf"], - shell=True, - executable='/bin/bash', - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - - out, _ = p.communicate() - if p.returncode == 0: - result[name]["configuration"] = "valid" - else: - result[name]["configuration"] = "broken" - result[name]["configuration-details"] = out.strip().split("\n") + result[name] = _get_and_format_service_status(name, infos) if len(names) == 1: return result[names[0]] @@ -407,6 +333,86 @@ def _get_service_information_from_systemd(service): return (unit, service) +def _get_and_format_service_status(service, infos): + + systemd_service = infos.get("actual_systemd_service", service) + raw_status, raw_service = _get_service_information_from_systemd(systemd_service) + + if raw_status is None: + logger.error("Failed to get status information via dbus for service %s, systemctl didn't recognize this service ('NoSuchUnit')." % systemd_service) + return { + 'status': "unknown", + 'start_on_boot': "unknown", + 'last_state_change': "unknown", + 'description': "Error: failed to get information for this service, it doesn't exists for systemd", + 'configuration': "unknown", + } + + translation_key = "service_description_%s" % service + description = infos.get("description") + if not description: + description = m18n.n(translation_key) + + # that mean that we don't have a translation for this string + # that's the only way to test for that for now + # if we don't have it, uses the one provided by systemd + if description == translation_key: + description = str(raw_status.get("Description", "")) + + output = { + 'status': str(raw_status.get("SubState", "unknown")), + 'start_on_boot': str(raw_status.get("UnitFileState", "unknown")), + 'last_state_change': "unknown", + 'description': description, + 'configuration': "unknown", + } + + # Fun stuff™ : to obtain the enabled/disabled status for sysv services, + # gotta do this ... cf code of /lib/systemd/systemd-sysv-install + if output["start_on_boot"] == "generated": + output["start_on_boot"] = "enabled" if glob("/etc/rc[S5].d/S??" + service) else "disabled" + elif os.path.exists("/etc/systemd/system/multi-user.target.wants/%s.service" % service): + output["start_on_boot"] = "enabled" + + if "StateChangeTimestamp" in raw_status: + output['last_state_change'] = datetime.utcfromtimestamp(raw_status["StateChangeTimestamp"] / 1000000) + + # 'test_status' is an optional field to test the status of the service using a custom command + if "test_status" in infos: + p = subprocess.Popen(infos["test_status"], + shell=True, + executable='/bin/bash', + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + + p.communicate() + + output["status"] = "running" if p.returncode == 0 else "failed" + elif raw_service.get("Type", "").lower() == "oneshot" and output["status"] == "exited": + # These are services like yunohost-firewall, hotspot, vpnclient, + # ... they will be "exited" why doesn't provide any info about + # the real state of the service (unless they did provide a + # test_status, c.f. previous condition) + output["status"] = "unknown" + + # 'test_status' is an optional field to test the status of the service using a custom command + if "test_conf" in infos: + p = subprocess.Popen(infos["test_conf"], + shell=True, + executable='/bin/bash', + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + + out, _ = p.communicate() + if p.returncode == 0: + output["configuration"] = "valid" + else: + output["configuration"] = "broken" + output["configuration-details"] = out.strip().split("\n") + + return output + + def service_log(name, number=50): """ Log every log files of a service From dd608baec5dfa79265dcf2d8a2f9d5efd9ed96ec Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 May 2020 00:07:35 +0200 Subject: [PATCH 120/482] Small simplification? --- src/yunohost/service.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index ddf34c9d0..4376ee6d3 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -286,26 +286,19 @@ def service_status(names=[]): # Filter only requested servivces services = {k: v for k, v in services.items() if k in names} - result = {} + # Remove services that aren't "real" services + # + # the historical reason is because regenconf has been hacked into the + # service part of YunoHost will in some situation we need to regenconf + # for things that aren't services + # the hack was to add fake services... + services = {k: v for k, v in services.items() if v.get("status", "") is not None} - for name, infos in services.items(): - - # this "service" isn't a service actually so we skip it - # - # the historical reason is because regenconf has been hacked into the - # service part of YunoHost will in some situation we need to regenconf - # for things that aren't services - # the hack was to add fake services... - # we need to extract regenconf from service at some point, also because - # some app would really like to use it - if infos.get("status", "") is None: - continue - - result[name] = _get_and_format_service_status(name, infos) + output = {s: _get_and_format_service_status(s, infos) for s, infos in services.items()} if len(names) == 1: - return result[names[0]] - return result + return output[names[0]] + return output def _get_service_information_from_systemd(service): From 1cd7ffea66d327985085693d97340c74b3e52d3e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 May 2020 00:20:28 +0200 Subject: [PATCH 121/482] Report unknown status for services as just a warning --- data/hooks/diagnosis/30-services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/hooks/diagnosis/30-services.py b/data/hooks/diagnosis/30-services.py index 6217d89d3..d0fe50ae9 100644 --- a/data/hooks/diagnosis/30-services.py +++ b/data/hooks/diagnosis/30-services.py @@ -21,7 +21,7 @@ class ServicesDiagnoser(Diagnoser): data={"status": result["status"], "configuration": result["configuration"]}) if result["status"] != "running": - item["status"] = "ERROR" + item["status"] = "ERROR" if result["status"] != "unknown" else "WARNING" item["summary"] = "diagnosis_services_bad_status" item["details"] = ["diagnosis_services_bad_status_tip"] From f8e5ea465243e8d95aa1799f7fe0b3125742c610 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sat, 16 May 2020 00:53:41 +0200 Subject: [PATCH 122/482] Fix tests, rely on _get_service_information_from_systemd to fetch service info during service add --- src/yunohost/service.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 4376ee6d3..1f77e3545 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -75,27 +75,32 @@ def service_add(name, description=None, log=None, log_type=None, test_status=Non service['log'] = log - if description: - service['description'] = description - else: + if not description: # Try to get the description from systemd service - out = subprocess.check_output("systemctl show %s | grep '^Description='" % name, shell=True).strip() - out = out.replace("Description=", "") + unit, _ = _get_service_information_from_systemd(name) + description = unit.get("Description", "") if unit is not None else "" # If the service does not yet exists or if the description is empty, # systemd will anyway return foo.service as default value, so we wanna # make sure there's actually something here. - if out == name + ".service": - logger.warning("/!\\ Packagers! You added a custom service without specifying a description. Please add a proper Description in the systemd configuration, or use --description to explain what the service does in a similar fashion to existing services.") - else: - service['description'] = out + if description == name + ".service": + description = "" + + if description: + service['description'] = description + else: + logger.warning("/!\\ Packagers! You added a custom service without specifying a description. Please add a proper Description in the systemd configuration, or use --description to explain what the service does in a similar fashion to existing services.") if need_lock: service['need_lock'] = True if test_status: service["test_status"] = test_status - elif subprocess.check_output("systemctl show %s | grep '^Type='" % name, shell=True).strip() == "oneshot": - logger.warning("/!\\ Packagers! Please provide a --test_status when adding oneshot-type services in Yunohost, such that it has a reliable way to check if the service is running or not.") + else: + # Try to get the description from systemd service + _, service = _get_service_information_from_systemd(name) + type_ = service.get("Type") if service is not None else "" + if type_ == "oneshot": + logger.warning("/!\\ Packagers! Please provide a --test_status when adding oneshot-type services in Yunohost, such that it has a reliable way to check if the service is running or not.") if test_conf: service["test_conf"] = test_conf From 108a3ca498081c80cdfd792b7b5ea7a5673a2ae8 Mon Sep 17 00:00:00 2001 From: Kayou Date: Sat, 16 May 2020 13:31:41 +0200 Subject: [PATCH 123/482] Update .gitlab-ci.yml --- .gitlab-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ff932c90..23a0075de 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -57,6 +57,12 @@ test-appurl: - cd src/yunohost - py.test tests/test_appurl.py +test-apps-arguments-parsing: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_apps_arguments_parsing.py + test-backuprestore: extends: .test-stage script: From 5c8c07b8c9019313a4c85c3c7400f4608e205b87 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 May 2020 03:24:26 +0200 Subject: [PATCH 124/482] More cleaning of app install logs: we don't really care about debug for ynh_wait_dpkg_free --- data/helpers.d/apt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index dcea0c976..03be6495c 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -10,6 +10,7 @@ # Requires YunoHost version 3.3.1 or higher. ynh_wait_dpkg_free() { local try + set +o xtrace # set +x # With seq 1 17, timeout will be almost 30 minutes for try in `seq 1 17` do @@ -32,13 +33,16 @@ ynh_wait_dpkg_free() { 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." + set -o xtrace # set -x return 1 fi done 9<<< "$(ls -1 $dpkg_dir)" + set -o xtrace # set -x return 0 fi done echo "apt still used, but timeout reached !" + set -o xtrace # set -x } # Check either a package is installed or not From 086db7a94b012c8414f74c7b3be5149b3e9364a2 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 May 2020 04:10:19 +0200 Subject: [PATCH 125/482] Need to explicitly convert info from dbusthingy to str :/ --- src/yunohost/service.py | 2 +- src/yunohost/tests/test_service.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index 1f77e3545..fe3ea830f 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -78,7 +78,7 @@ def service_add(name, description=None, log=None, log_type=None, test_status=Non if not description: # Try to get the description from systemd service unit, _ = _get_service_information_from_systemd(name) - description = unit.get("Description", "") if unit is not None else "" + description = str(unit.get("Description", "")) if unit is not None else "" # If the service does not yet exists or if the description is empty, # systemd will anyway return foo.service as default value, so we wanna # make sure there's actually something here. diff --git a/src/yunohost/tests/test_service.py b/src/yunohost/tests/test_service.py index ffe3629c5..c51073c54 100644 --- a/src/yunohost/tests/test_service.py +++ b/src/yunohost/tests/test_service.py @@ -25,7 +25,11 @@ def clean(): if "dummyservice" in services: del services["dummyservice"] - _save_services(services) + + if "networking" in services: + del services["networking"] + + _save_services(services) def test_service_status_all(): @@ -60,6 +64,10 @@ def test_service_add(): service_add("dummyservice", description="A dummy service to run tests") assert "dummyservice" in service_status().keys() +def test_service_add_real_service() + + service_add("networking") + assert "networking" in service_status().keys() def test_service_remove(): From 7b4a9b57bc0241dda975d15ee662cfc46a5c340d Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 May 2020 04:22:15 +0200 Subject: [PATCH 126/482] Stewpeed typo :| --- src/yunohost/tests/test_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yunohost/tests/test_service.py b/src/yunohost/tests/test_service.py index c51073c54..f91a601c4 100644 --- a/src/yunohost/tests/test_service.py +++ b/src/yunohost/tests/test_service.py @@ -64,7 +64,7 @@ def test_service_add(): service_add("dummyservice", description="A dummy service to run tests") assert "dummyservice" in service_status().keys() -def test_service_add_real_service() +def test_service_add_real_service(): service_add("networking") assert "networking" in service_status().keys() From 2d2b3e6bb6f0a9b1c66d51143633afe4ad9b5977 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 May 2020 04:01:56 +0200 Subject: [PATCH 127/482] Rework ynh_psql_test_if_first_run --- data/helpers.d/postgresql | 73 ++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index e2bef8746..78ef4f7ce 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -1,6 +1,7 @@ #!/bin/bash PSQL_ROOT_PWD_FILE=/etc/yunohost/psql +PSQL_VERSION=9.6 # Open a connection as a user # @@ -273,6 +274,7 @@ ynh_psql_remove_db() { } # Create a master password and set up global settings +# It also make sure that postgresql is installed and running # Please always call this script in install and restore scripts # # usage: ynh_psql_test_if_first_run @@ -280,45 +282,38 @@ ynh_psql_remove_db() { # Requires YunoHost version 2.7.13 or higher. ynh_psql_test_if_first_run() { - if [ -f "$PSQL_ROOT_PWD_FILE" ] + # Make sure postgresql is indeed installed + dpkg --list | grep -q "ii postgresql-$PSQL_VERSION" || ynh_die "postgresql-$PSQL_VERSION is not installed !?" + + # Check for some weird issue where postgresql could be installed but etc folder would not exist ... + [ -e "/etc/postgresql/$PSQL_VERSION" ] || ynh_die "It looks like postgresql was not properly configured ? /etc/postgresql/$PSQL_VERSION is missing ... Could be due to a locale issue, c.f.https://serverfault.com/questions/426989/postgresql-etc-postgresql-doesnt-exist" + + # Make sure postgresql is started and enabled + # (N.B. : to check the active state, we check the cluster state because + # postgresql could be flagged as active even though the cluster is in + # failed state because of how the service is configured..) + systemctl is-active postgresql@$PSQL_VERSION-main -q || ynh_systemd_action --service_name=postgresql --action=restart + systemctl is-enabled postgresql -q || systemctl enable postgresql + + # If this is the very first time, we define the root password + # and configure a few things + if [ ! -f "$PSQL_ROOT_PWD_FILE" ] then - ynh_print_info --message="PostgreSQL is already installed, no need to create master password" - return + local pg_hba=/etc/postgresql/$PSQL_VERSION/main/pg_hba.conf + + local psql_root_password="$(ynh_string_random)" + echo "$psql_root_password" >$PSQL_ROOT_PWD_FILE + sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$psql_root_password'" postgres + + # force all user to connect to local databases using hashed 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 + ynh_replace_string --match_string="local\(\s*\)all\(\s*\)all\(\s*\)peer" --replace_string="local\1all\2all\3md5" --target_file="$pg_hba" + + # Integrate postgresql service in yunohost + yunohost service add postgresql --log "/var/log/postgresql/" + + ynh_systemd_action --service_name=postgresql --action=reload fi - - local psql_root_password="$(ynh_string_random)" - echo "$psql_root_password" >$PSQL_ROOT_PWD_FILE - - 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 - if dpkg --list | grep -q "ii postgresql-9." - then - ynh_die "It looks like postgresql was not properly configured ? /etc/postgresql/9.* is missing ... Could be due to a locale issue, c.f.https://serverfault.com/questions/426989/postgresql-etc-postgresql-doesnt-exist" - else - ynh_die "postgresql shoud be 9.4 or 9.6" - fi - fi - - ynh_systemd_action --service_name=postgresql --action=start - - sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$psql_root_password'" postgres - - # force all user to connect to local databases using hashed 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 - ynh_replace_string --match_string="local\(\s*\)all\(\s*\)all\(\s*\)peer" --replace_string="local\1all\2all\3md5" --target_file="$pg_hba" - - # Advertise service in admin panel - yunohost service add postgresql --log "$logfile" - - systemctl enable postgresql - ynh_systemd_action --service_name=postgresql --action=reload } From 7d284e8447f5b3ae17ea6f6e216af11bc4fd71f6 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sat, 16 May 2020 14:18:52 +0200 Subject: [PATCH 128/482] [enh] build and install deb --- .gitlab-ci.yml | 105 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 23a0075de..b474b92c1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,106 @@ stages: + - build + - install - postinstall - tests - lint +######################################## +# BUILD DEB +######################################## + +.build-stage: + image: before-install + stage: build + variables: + YNH_BUILD_DIR: "ynh-build" + YNH_SOURCE: "https://github.com/yunohost" + before_script: + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" install git git-buildpackage postfix python-setuptools + - mkdir -p $YNH_BUILD_DIR + - cd $YNH_BUILD_DIR + - git clone $YNH_SOURCE/$DEB_TO_BUILD + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$DEB_TO_BUILD + cache: + paths: + - $YNH_BUILD_DIR/*.deb + key: "$CI_COMMIT_REF_SLUG" + +build-yunohost: + extends: .build-stage + variables: + DEB_TO_BUILD: "yunohost" + script: + - cd $DEB_TO_BUILD + - debuild -us -uc + +build-ssowat: + extends: .build-stage + variables: + DEB_TO_BUILD: "ssowat" + script: + - cd $DEB_TO_BUILD + - debuild -us -uc + +build-moulinette: + extends: .build-stage + variables: + DEB_TO_BUILD: "moulinette" + script: + - cd $DEB_TO_BUILD + - debuild -us -uc + +build-metronome: + extends: .build-stage + variables: + DEB_TO_BUILD: "metronome" + script: + - cd $DEB_TO_BUILD + - dpkg-buildpackage -rfakeroot -uc -b -d + +######################################## +# INSTALL DEB +######################################## + +install: + image: before-install + stage: install + variables: + YNH_BUILD_DIR: "ynh-build" + script: + - | + debconf-set-selections << EOF + slapd slapd/password1 password yunohost + slapd slapd/password2 password yunohost + slapd slapd/domain string yunohost.org + slapd shared/organization string yunohost.org + slapd slapd/allow_ldap_v2 boolean false + slapd slapd/invalid_config boolean true + slapd slapd/backend select MDB + postfix postfix/main_mailer_type select Internet Site + postfix postfix/mailname string /etc/mailname + mariadb-server-10.1 mysql-server/root_password password yunohost + mariadb-server-10.1 mysql-server/root_password_again password yunohost + nslcd nslcd/ldap-bindpw password + nslcd nslcd/ldap-starttls boolean false + nslcd nslcd/ldap-reqcert select + nslcd nslcd/ldap-uris string ldap://localhost/ + nslcd nslcd/ldap-binddn string + nslcd nslcd/ldap-base string dc=yunohost,dc=org + libnss-ldapd libnss-ldapd/nsswitch multiselect group, passwd, shadow + postsrsd postsrsd/domain string yunohost.org + EOF + - cd $YNH_BUILD_DIR + - SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./*.deb + artifacts: + paths: + - $YNH_BUILD_DIR/*.deb + cache: + paths: + - $YNH_BUILD_DIR/ + policy: pull + key: "$CI_COMMIT_REF_SLUG" + ######################################## # POSTINSTALL ######################################## @@ -21,16 +119,17 @@ postinstall: .test-stage: image: after-postinstall stage: tests + variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" before_script: - apt-get install python-pip -y - - mkdir -p .pip - pip install -U pip - hash -d pip - - pip --cache-dir=.pip install pytest pytest-sugar pytest-mock requests-mock mock + - pip install pytest pytest-sugar pytest-mock requests-mock mock - export PYTEST_ADDOPTS="--color=yes" cache: paths: - - .pip + - .cache/pip - src/yunohost/tests/apps key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" From 7f4b0ce6e3c89b290ce4fe63614af3fd9699e3b2 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 17 May 2020 15:02:58 +0200 Subject: [PATCH 129/482] Add YNH repo before install --- .gitlab-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b474b92c1..ee6caee48 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -67,6 +67,11 @@ install: stage: install variables: YNH_BUILD_DIR: "ynh-build" + before_script: + - apt install wget --assume-yes + - echo "deb http://forge.yunohost.org/debian/ stretch stable testing unstable" > /etc/apt/sources.list.d/yunohost.list + - wget -O- https://forge.yunohost.org/yunohost.asc -q | apt-key add -qq - >/dev/null 2>&1 + - apt update script: - | debconf-set-selections << EOF From f73c34bfc11ef1ee1bbf10d75b5699b3b6a1ee07 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Sun, 17 May 2020 17:00:33 +0200 Subject: [PATCH 130/482] Tell systemctl to stfu when enabling/disabling services, just do it --- src/yunohost/service.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/yunohost/service.py b/src/yunohost/service.py index fe3ea830f..bc082da21 100644 --- a/src/yunohost/service.py +++ b/src/yunohost/service.py @@ -515,6 +515,9 @@ def _run_service_command(action, service): need_lock = services[service].get('need_lock', False) \ and action in ['start', 'stop', 'restart', 'reload', 'reload-or-restart'] + if action in ["enable", "disable"]: + cmd += " --quiet" + try: # Launch the command logger.debug("Running '%s'" % cmd) From 01f8ee6b7bd515fa03bb46d275b853f3a3d28a72 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 17 May 2020 18:29:10 +0200 Subject: [PATCH 131/482] fix stupid fail2ban issue --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ee6caee48..81dc3aa97 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -72,6 +72,8 @@ install: - echo "deb http://forge.yunohost.org/debian/ stretch stable testing unstable" > /etc/apt/sources.list.d/yunohost.list - wget -O- https://forge.yunohost.org/yunohost.asc -q | apt-key add -qq - >/dev/null 2>&1 - apt update + # https://github.com/YunoHost/install_script/blob/3e16abd7c4e1fe9c518cbc573282cb8fb1fcbbd7/install_yunohost#L433-L485 + - touch /var/log/auth.log script: - | debconf-set-selections << EOF From 24d83b6a97d7aa406595bf855702f9ee7c3f1b7e Mon Sep 17 00:00:00 2001 From: Kay0u Date: Sun, 17 May 2020 19:41:38 +0200 Subject: [PATCH 132/482] fix avahi install --- .gitlab-ci.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 81dc3aa97..7777e283b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -74,6 +74,15 @@ install: - apt update # https://github.com/YunoHost/install_script/blob/3e16abd7c4e1fe9c518cbc573282cb8fb1fcbbd7/install_yunohost#L433-L485 - touch /var/log/auth.log + - > + if ! id avahi > /dev/null 2>&1; then + avahi_id=$((500 + RANDOM % 500)) + while cut -d ':' -f 3 /etc/passwd | grep -q $avahi_id + do + avahi_id=$((500 + RANDOM % 500)) + done + adduser --disabled-password --quiet --system --home /var/run/avahi-daemon --no-create-home --gecos "Avahi mDNS daemon" --group avahi --uid $avahi_id + fi script: - | debconf-set-selections << EOF @@ -98,7 +107,7 @@ install: postsrsd postsrsd/domain string yunohost.org EOF - cd $YNH_BUILD_DIR - - SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./*.deb + - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./*.deb artifacts: paths: - $YNH_BUILD_DIR/*.deb From fc30d82df5a6aaae305489021c26b225530b398b Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 May 2020 00:27:42 +0200 Subject: [PATCH 133/482] Ugly hack to workaround sury pinning issues when installing dependencies --- data/helpers.d/apt | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 03be6495c..74862eca5 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -193,17 +193,37 @@ ynh_package_install_from_equivs () { LC_ALL=C equivs-build ./control 1> /dev/null dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1) - ynh_package_install --fix-broken || \ + # Let's try to see if install will work using dry-run. It it fails, + # it could be because the pinning of sury is blocking some package install + # c.f. for example: https://github.com/YunoHost/issues/issues/1563#issuecomment-623406509 + # ... In that case, we use an ugly hack were we'll use a tweaked + # preferences.d directory with looser contrains for sury... + if ! ynh_package_install --fix-broken --dry-run >/dev/null 2>&1 && [ -e /etc/apt/preferences.d/extra_php_version ] + then + cp -r /etc/apt/preferences.d/ /etc/apt/preferences.d.tmp/ + sed 's/^Pin-Priority: .*/Pin-Priority: 600/g' -i /etc/apt/preferences.d.tmp/extra_php_version + local apt_tweaks='--option Dir::Etc::preferencesparts=preferences.d.tmp' + # Try a dry-run again to see if that fixes the issue ... + # If it did not, then that's probably not related to sury. + ynh_package_install $apt_tweaks --fix-broken --dry-run >/dev/null 2>&1 || apt_tweaks="" + else + local apt_tweaks="" + fi + + # Try to install for real + ynh_package_install $apt_tweaks --fix-broken || \ { # If the installation failed # (the following is ran inside { } to not start a subshell otherwise ynh_die wouldnt exit the original process) + rm --recursive --force /etc/apt/preferences.d.tmp/ # Get the list of dependencies from the deb local dependencies="$(dpkg --info "$TMPDIR/${pkgname}_${pkgversion}_all.deb" | grep Depends | \ sed 's/^ Depends: //' | sed 's/,//g')" # Fake an install of those dependencies to see the errors # The sed command here is, Print only from '--fix-broken' to the end. - ynh_package_install $dependencies --dry-run | sed --quiet '/--fix-broken/,$p' >&2 + ynh_package_install $apt_tweaks $dependencies --dry-run | sed --quiet '/--fix-broken/,$p' >&2 ynh_die --message="Unable to install dependencies"; } [[ -n "$TMPDIR" ]] && rm --recursive --force $TMPDIR # Remove the temp dir. + rm --recursive --force /etc/apt/preferences.d.tmp/ # check if the package is actually installed ynh_package_is_installed "$pkgname" From 94ea82651839b0e59ce05a61d33d1a49e40bf292 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 May 2020 01:31:37 +0200 Subject: [PATCH 134/482] Most of the time there's no .ini file and it still displays an info about the file not existing when attempting to remove it --- data/helpers.d/php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/helpers.d/php b/data/helpers.d/php index e8de6d9ff..9b9df64f9 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -297,7 +297,10 @@ ynh_remove_fpm_config () { fi ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" - ynh_exec_warn_less ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" + if [ -e $fpm_config_dir/conf.d/20-$app.ini ] + then + ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" + fi # If the php version used is not the default version for YunoHost if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] From 09ff411664e4f93c339df2b5338bfbddaec69293 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 18 May 2020 15:11:31 +0200 Subject: [PATCH 135/482] rework build scripts --- .gitlab-ci.yml | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7777e283b..c0c49d447 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,45 +18,48 @@ stages: before_script: - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" install git git-buildpackage postfix python-setuptools - mkdir -p $YNH_BUILD_DIR - - cd $YNH_BUILD_DIR - - git clone $YNH_SOURCE/$DEB_TO_BUILD - - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$DEB_TO_BUILD cache: paths: - $YNH_BUILD_DIR/*.deb key: "$CI_COMMIT_REF_SLUG" +.build_script: &build_script | + cd $YNH_BUILD_DIR/$PACKAGE + VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null) + VERSION_NIGHTLY="${VERSION}+$CI_PIPELINE_ID+$(date +%Y%m%d%H%M)" + dch --package "${PACKAGE}" --force-bad-version -v "${VERSION_NIGHTLY}" -D "unstable" --force-distribution "Daily build." + debuild -us -uc + build-yunohost: extends: .build-stage variables: - DEB_TO_BUILD: "yunohost" + PACKAGE: "yunohost" script: - - cd $DEB_TO_BUILD - - debuild -us -uc + - git ls-files | xargs tar -czf archive.tar.gz + - mkdir -p $YNH_BUILD_DIR/$PACKAGE + - cat archive.tar.gz | tar -xz -C $YNH_BUILD_DIR/$PACKAGE + - rm archive.tar.gz + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE + - *build_script + build-ssowat: extends: .build-stage variables: - DEB_TO_BUILD: "ssowat" + PACKAGE: "ssowat" script: - - cd $DEB_TO_BUILD - - debuild -us -uc + - git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE + - *build_script build-moulinette: extends: .build-stage variables: - DEB_TO_BUILD: "moulinette" + PACKAGE: "moulinette" script: - - cd $DEB_TO_BUILD - - debuild -us -uc - -build-metronome: - extends: .build-stage - variables: - DEB_TO_BUILD: "metronome" - script: - - cd $DEB_TO_BUILD - - dpkg-buildpackage -rfakeroot -uc -b -d + - git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE + - *build_script ######################################## # INSTALL DEB From 85442c42dcc6462f17fe90ce3f1b6c5efc2febae Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 18 May 2020 15:11:38 +0200 Subject: [PATCH 136/482] fix install --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c0c49d447..e48cb62bb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -86,6 +86,7 @@ install: done adduser --disabled-password --quiet --system --home /var/run/avahi-daemon --no-create-home --gecos "Avahi mDNS daemon" --group avahi --uid $avahi_id fi + - apt install --assume-yes debhelper script: - | debconf-set-selections << EOF From 471dc025dbb6cab18d3c7441348ed9bd427432b5 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 18 May 2020 15:24:42 +0200 Subject: [PATCH 137/482] move debhelper install [skip ci] --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e48cb62bb..b58a8868e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -71,7 +71,7 @@ install: variables: YNH_BUILD_DIR: "ynh-build" before_script: - - apt install wget --assume-yes + - apt install --assume-yes wget debhelper - echo "deb http://forge.yunohost.org/debian/ stretch stable testing unstable" > /etc/apt/sources.list.d/yunohost.list - wget -O- https://forge.yunohost.org/yunohost.asc -q | apt-key add -qq - >/dev/null 2>&1 - apt update @@ -86,7 +86,6 @@ install: done adduser --disabled-password --quiet --system --home /var/run/avahi-daemon --no-create-home --gecos "Avahi mDNS daemon" --group avahi --uid $avahi_id fi - - apt install --assume-yes debhelper script: - | debconf-set-selections << EOF From 09ebed1d0b67bea9af19457ff4171e338a046f8d Mon Sep 17 00:00:00 2001 From: Kay0u Date: Mon, 18 May 2020 15:44:53 +0200 Subject: [PATCH 138/482] change deb name --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b58a8868e..befa66c1e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,12 +21,12 @@ stages: cache: paths: - $YNH_BUILD_DIR/*.deb - key: "$CI_COMMIT_REF_SLUG" + key: "$CI_PIPELINE_ID" .build_script: &build_script | cd $YNH_BUILD_DIR/$PACKAGE VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null) - VERSION_NIGHTLY="${VERSION}+$CI_PIPELINE_ID+$(date +%Y%m%d%H%M)" + VERSION_NIGHTLY="${VERSION}~${CI_COMMIT_REF_SLUG//-}+$(date +%Y%m%d%H%M)" dch --package "${PACKAGE}" --force-bad-version -v "${VERSION_NIGHTLY}" -D "unstable" --force-distribution "Daily build." debuild -us -uc @@ -118,7 +118,7 @@ install: paths: - $YNH_BUILD_DIR/ policy: pull - key: "$CI_COMMIT_REF_SLUG" + key: "$CI_PIPELINE_ID" ######################################## # POSTINSTALL From f9e4c96ca3de5653e109460d18edcf809371897a Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 May 2020 18:49:15 +0200 Subject: [PATCH 139/482] Crash early about apps already installed when attempting to restore --- locales/en.json | 1 + src/yunohost/backup.py | 32 ++++++++++++++++-------- src/yunohost/tests/test_backuprestore.py | 7 +++--- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/locales/en.json b/locales/en.json index 25e5a500a..95e297bc1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -534,6 +534,7 @@ "regenconf_failed": "Could not regenerate the configuration for category(s): {categories}", "regenconf_pending_applying": "Applying pending configuration for category '{category}'…", "restore_already_installed_app": "An app with the ID '{app:s}' is already installed", + "restore_already_installed_apps": "The following apps can't be restored because they are already installed: {apps}", "restore_app_failed": "Could not restore the app '{app:s}'", "restore_cleaning_failed": "Could not clean up the temporary restoration directory", "restore_complete": "Restored", diff --git a/src/yunohost/backup.py b/src/yunohost/backup.py index 1948e795c..449b52bd8 100644 --- a/src/yunohost/backup.py +++ b/src/yunohost/backup.py @@ -1004,10 +1004,20 @@ class RestoreManager(): logger.error(m18n.n('backup_archive_app_not_found', app=app)) - self.targets.set_wanted("apps", - apps, - self.info['apps'].keys(), - unknown_error) + to_be_restored = self.targets.set_wanted("apps", + apps, + self.info['apps'].keys(), + unknown_error) + + # If all apps to restore are already installed, stop right here. + # Otherwise, if at least one app can be restored, we keep going on + # because those which can be restored will indeed be restored + already_installed = [app for app in to_be_restored if _is_installed(app)] + if already_installed != []: + if already_installed == to_be_restored: + raise YunohostError("restore_already_installed_apps", apps=', '.join(already_installed)) + else: + logger.warning(m18n.n("restore_already_installed_apps", apps=', '.join(already_installed))) # # Archive mounting # @@ -1301,13 +1311,6 @@ class RestoreManager(): else: shutil.copy2(s, d) - # Start register change on system - related_to = [('app', app_instance_name)] - 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', @@ -1315,6 +1318,13 @@ class RestoreManager(): self.targets.set_result("apps", app_instance_name, "Error") return + # Start register change on system + related_to = [('app', app_instance_name)] + operation_logger = OperationLogger('backup_restore_app', related_to) + operation_logger.start() + + logger.info(m18n.n("app_start_restore", app=app_instance_name)) + app_dir_in_archive = os.path.join(self.work_dir, 'apps', app_instance_name) app_backup_in_archive = os.path.join(app_dir_in_archive, 'backup') app_settings_in_archive = os.path.join(app_dir_in_archive, 'settings') diff --git a/src/yunohost/tests/test_backuprestore.py b/src/yunohost/tests/test_backuprestore.py index 790d27d6c..aa443f2a5 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -475,10 +475,9 @@ def test_restore_app_already_installed(mocker): assert _is_installed("wordpress") - with message(mocker, 'restore_already_installed_app', app="wordpress"): - with raiseYunohostError(mocker, 'restore_nothings_done'): - backup_restore(system=None, name=backup_list()["archives"][0], - apps=["wordpress"]) + with message(mocker, 'restore_already_installed_apps', apps="wordpress"): + backup_restore(system=None, name=backup_list()["archives"][0], + apps=["wordpress"]) assert _is_installed("wordpress") From 0dde1f6d4fed6c434d618dd3d5de3cf659304345 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 18 May 2020 19:57:54 +0200 Subject: [PATCH 140/482] Fix exception assertion --- 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 aa443f2a5..026e87c95 100644 --- a/src/yunohost/tests/test_backuprestore.py +++ b/src/yunohost/tests/test_backuprestore.py @@ -475,7 +475,7 @@ def test_restore_app_already_installed(mocker): assert _is_installed("wordpress") - with message(mocker, 'restore_already_installed_apps', apps="wordpress"): + with raiseYunohostError(mocker, 'restore_already_installed_apps'): backup_restore(system=None, name=backup_list()["archives"][0], apps=["wordpress"]) From d7891970c3564f6906b20dd44daf6d5744f69bf0 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 May 2020 19:56:04 +0200 Subject: [PATCH 141/482] Clean unused code/imports --- src/yunohost/app.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 839abee81..b9116693b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -35,14 +35,13 @@ import subprocess import glob import urllib from collections import OrderedDict -from datetime import datetime from moulinette import msignals, m18n, msettings from moulinette.utils.log import getActionLogger from moulinette.utils.network import download_json from moulinette.utils.filesystem import read_file, read_json, read_toml, read_yaml, write_to_file, write_to_json, write_to_yaml, chmod, chown, mkdir -from yunohost.service import service_log, service_status, _run_service_command +from yunohost.service import service_status, _run_service_command from yunohost.utils import packages from yunohost.utils.error import YunohostError from yunohost.log import is_unit_operation, OperationLogger @@ -2797,21 +2796,6 @@ def is_true(arg): return True if arg else False -def random_password(length=8): - """ - Generate a random string - - Keyword arguments: - length -- The string length to generate - - """ - import string - import random - - char_set = string.ascii_uppercase + string.digits + string.ascii_lowercase - return ''.join([random.SystemRandom().choice(char_set) for x in range(length)]) - - def unstable_apps(): output = [] From 17a439e9ea483e6bc596cc79ca4d701730546a82 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 May 2020 20:18:33 +0200 Subject: [PATCH 142/482] Update changelog for 3.8.4.2 --- debian/changelog | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/debian/changelog b/debian/changelog index 139d390a5..e4991cb0c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,21 @@ +yunohost (3.8.4.2) testing; urgency=low + + - [enh] During failed upgrades: Only mention packages that couldn't be upgraded (26fcfed7) + - [enh] Also run dpkg --audit to check if dpkg is in a broken state (09d8500f, 97199d19) + - [enh] Improve logs readability (c6f18496, 9cbd368d, 5850bf61, 413778d2, 5c8c07b8, f73c34bf, 94ea8265) + - [enh] Crash early about apps already installed when attempting to restore (f9e4c96c) + - [fix] Add the damn short hostname to /etc/hosts automagically (c.f. rabbitmq-server) (e67dc791) + - [fix] Don't miserably crash if doveadm fails to run (c9b22138) + - [fix] Diagnosis: Try to not have weird warnings if no diagnosis ran yet... (65c87d55) + - [fix] Diagnosis: Change logic of --email to avoid sending empty mail if some issues are found but ignored (4cd4938e) + - [enh] Diagnosis/services: Report the service status as warning/unknown if service type is oneshot and status exited (dd09758f, 1cd7ffea) + - [fix] Rework ynh_psql_test_if_first_run ([#993](https://github.com/yunohost/yunohost/pull/993)) + - [tests] Tests for args parsing ([#989](https://github.com/yunohost/yunohost/pull/989), 108a3ca4) + + Thanks to all contributors <3 ! (Bram, Kayou) + + -- Alexandre Aubin Tue, 19 May 2020 20:08:47 +0200 + yunohost (3.8.4.1) testing; urgency=low - [mod] Tweak diagnosis threshold for swap warning (429df8c4) From 188bf2f77a04757138f7d6499ff949e5e32998a7 Mon Sep 17 00:00:00 2001 From: clecle226 Date: Sun, 10 May 2020 12:39:01 +0000 Subject: [PATCH 143/482] Translated using Weblate (French) Currently translated at 99.8% (637 of 638 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/fr/ --- locales/fr.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 96d815b1a..509a30844 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -166,7 +166,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} n’est pas sur le point d’expirer ! (Vous pouvez utiliser --force si vous savez ce que vous faites)", - "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_domain_http_not_working": "Le domaine {domain:s} ne semble pas être accessible via HTTP. Merci de vérifier la catégorie 'Web' dans le diagnostic pour plus d'informations. (Ou si vous savez ce que vous faites, utilisé '--no-checks' pour désactiver la vérification.)", "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 (des vérificateurs 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}", @@ -647,5 +647,7 @@ "diagnosis_domain_expiration_success": "Vos domaines sont enregistrés et ne vont pas expirer prochainement.", "diagnosis_domain_expiration_warning": "Certains domaines vont expirer prochainement !", "diagnosis_domain_expiration_error": "Certains domaines vont expirer TRÈS PROCHAINEMENT !", - "diagnosis_domain_expires_in": "Le {domain} expire dans {days} jours." + "diagnosis_domain_expires_in": "{domain} expire dans {days} jours.", + "certmanager_domain_not_diagnosed_yet": "Il n'y a pas encore de résultat de diagnostic pour le domaine %s. Merci de relancer un diagnostic pour les catégories 'Enregistrements DNS' et 'Web' dans la section Diagnostique pour vérifier si le domaine est prêt pour Let's Encrypt. (Ou si vous savez ce que vous faites, utilisez '--no-checks' pour désactiver la vérification.)", + "diagnosis_swap_tip": "Merci d'être prudent et conscient que si vous hébergez une partition SWAP sur une carte SD ou un disque SSD, cela risque de réduire drastiquement l’espérance de vie du périphérique." } From 34a9fbf3edf63b13ddd8cc89f9bd4f3314ced6dc Mon Sep 17 00:00:00 2001 From: ppr Date: Sun, 10 May 2020 13:50:22 +0000 Subject: [PATCH 144/482] Translated using Weblate (French) Currently translated at 99.8% (637 of 638 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 509a30844..ced8d92be 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -333,7 +333,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": "Reconfigurer les espaces utilisateurs PHP pour utiliser PHP 7 au lieu de PHP 5", + "migration_description_0004_php5_to_php7_pools": "Reconfigurez l'ensemble 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 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 est installé, mais pas PostgreSQL 9.6 ‽ Quelque chose de bizarre aurait pu se produire sur votre système :(…", From c92eee337b2ab21503bcc6e34740a4c72244cc44 Mon Sep 17 00:00:00 2001 From: xaloc33 Date: Sat, 9 May 2020 18:45:50 +0000 Subject: [PATCH 145/482] Translated using Weblate (Catalan) Currently translated at 100.0% (638 of 638 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/ca/ --- locales/ca.json | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 234a32fe4..64ee60477 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -100,7 +100,7 @@ "backup_unable_to_organize_files": "No s'ha pogut utilitzar el mètode ràpid per organitzar els fitxers dins de l'arxiu", "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_acme_not_configured_for_domain": "No s'ha pogut executar el ACME challenge pel domini {domain} en aquests moments ja que a la seva configuració de nginx li manca el codi corresponent… Assegureu-vos que la configuració nginx està actualitzada utilitzant «yunohost tools regen-conf nginx --dry-run --with-diff».", "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 si sabeu el que esteu fent)", "certmanager_attempt_to_replace_valid_cert": "Esteu intentant sobreescriure un certificat correcte i vàlid pel domini {domain:s}! (Utilitzeu --force per ometre)", @@ -113,8 +113,8 @@ "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. Verifiqueu que les configuracions DNS i NGINX siguin correctes", + "certmanager_domain_dns_ip_differs_from_public_ip": "Els registres DNS pel domini «{domain:s}» són diferents a l'adreça IP d'aquest servidor. Mireu la categoria «registres DNS» (bàsic) al diagnòstic per a més informació. 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": "El domini {domain:s} sembla que no és accessible via HTTP. Verifiqueu la categoria «Web» en el diagnòstic per a més informació. (Si sabeu el que esteu fent, utilitzeu «--no-checks» per deshabilitar les 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", @@ -140,7 +140,7 @@ "domain_dyndns_already_subscribed": "Ja us heu subscrit a un domini DynDNS", "domain_dyndns_root_unknown": "Domini DynDNS principal desconegut", "domain_hostname_failed": "No s'ha pogut establir un nou nom d'amfitrió. Això podria causar problemes més tard (podria no passar res).", - "domain_uninstall_app_first": "Hi ha una o més aplicacions instal·lades en aquest domini. Desinstal·leu les abans d'eliminar el domini", + "domain_uninstall_app_first": "Aquestes aplicacions encara estan instal·lades en el vostre domini: {apps}. Desinstal·leu les abans d'eliminar el domini", "domain_unknown": "Domini desconegut", "domains_available": "Dominis disponibles:", "done": "Fet", @@ -634,9 +634,19 @@ "diagnosis_ports_partially_unreachable": "El port {port} no és accessible des de l'exterior amb IPv{failed}.", "diagnosis_http_partially_unreachable": "El domini {domain} sembla que no és accessible utilitzant HTTP des de l'exterior de la xarxa local amb IPv{failed}, tot i que funciona amb IPv{passed}.", "diagnosis_mail_fcrdns_nok_details": "Hauríeu d'intentar configurar primer el DNS invers amb {ehlo_domain} en la interfície del router o en la interfície del vostre allotjador. (Alguns allotjadors requereixen que obris un informe de suport per això).", - "diagnosis_mail_fcrdns_nok_alternatives_4": "Alguns proveïdors no permeten configurar el DNS invers (o aquesta funció pot no funcionar…). Si teniu problemes a causa d'això, considereu les solucions següents:
- Alguns proveïdors d'accés a internet (ISP) donen l'alternativa de utilitzar un relay de servidor de correu electrònic tot i que implica que el relay podrà espiar el trànsit de correus electrònics.
- Una alternativa respectuosa amb la privacitat és utilitzar una VPN *amb una IP pública dedicada* per sobrepassar aquest tipus de limitacions. Mireu https://yunohost.org/#/vpn_advantage
- Finalment, també es pot canviar de proveïdor", + "diagnosis_mail_fcrdns_nok_alternatives_4": "Alguns proveïdors no permeten configurar el DNS invers (o aquesta funció pot no funcionar…). Si teniu problemes a causa d'això, considereu les solucions següents:
- Alguns proveïdors d'accés a internet (ISP) donen l'alternativa de utilitzar un relay de servidor de correu electrònic tot i que implica que el relay podrà espiar el trànsit de correus electrònics.
- Una alternativa respectuosa amb la privacitat és utilitzar una VPN *amb una IP pública dedicada* per sobrepassar aquest tipus de limitacions. Mireu https://yunohost.org/#/vpn_advantage
- O es pot canviar a un proveïdor diferent", "diagnosis_mail_fcrdns_nok_alternatives_6": "Alguns proveïdors no permeten configurar el vostre DNS invers (o la funció no els hi funciona…). Si el vostre DNS invers està correctament configurat per IPv4, podeu intentar deshabilitar l'ús de IPv6 per a enviar correus electrònics utilitzant yunohost settings set smtp.allow_ipv6 -v off. Nota: aquesta última solució implica que no podreu enviar o rebre correus electrònics cap a els pocs servidors que hi ha que només tenen IPv-6.", "diagnosis_http_hairpinning_issue_details": "Això és probablement a causa del router del vostre proveïdor d'accés a internet. El que fa, que gent de fora de la xarxa local pugui accedir al servidor sense problemes, però no la gent de dins la xarxa local (com vostè probablement) quan s'utilitza el nom de domini o la IP global. Podreu segurament millorar la situació fent una ullada a https://yunohost.org/dns_local_network", "backup_archive_cant_retrieve_info_json": "No s'ha pogut carregar la informació de l'arxiu «{archive}»… No s'ha pogut obtenir el fitxer info.json (o no és un fitxer json vàlid).", - "backup_archive_corrupted": "Sembla que l'arxiu de la còpia de seguretat «{archive}» està corromput : {error}" + "backup_archive_corrupted": "Sembla que l'arxiu de la còpia de seguretat «{archive}» està corromput : {error}", + "certmanager_domain_not_diagnosed_yet": "Encara no hi ha cap resultat de diagnòstic per al domini %s. Torneu a executar el diagnòstic per a les categories «Registres DNS» i «Web» en la secció de diagnòstic per comprovar que el domini està preparat per a Let's Encrypt. (O si sabeu el que esteu fent, utilitzant «--no-checks» per deshabilitar les comprovacions.)", + "diagnosis_ip_no_ipv6_tip": "Utilitzar una IPv6 no és obligatori per a que funcioni el servidor, però és millor per la salut d'Internet en conjunt. La IPv6 hauria d'estar configurada automàticament pel sistema o pel proveïdor si està disponible. Si no és el cas, pot ser necessari configurar alguns paràmetres més de forma manual tal i com s'explica en la documentació disponible aquí: https://yunohost.org/#/ipv6. Si no podeu habilitar IPv6 o us sembla massa tècnic, podeu ignorar aquest avís sense problemes.", + "diagnosis_domain_expiration_not_found": "No s'ha pogut comprovar la data d'expiració d'alguns dominis", + "diagnosis_domain_not_found_details": "El domini {domain} no existeix en la base de dades WHOIS o ha expirat!", + "diagnosis_domain_expiration_not_found_details": "La informació WHOIS pel domini {domain} sembla que no conté informació sobre la data d'expiració?", + "diagnosis_domain_expiration_success": "Els vostres dominis estan registrats i no expiraran properament.", + "diagnosis_domain_expiration_warning": "Alguns dominis expiraran properament!", + "diagnosis_domain_expiration_error": "Alguns dominis expiraran EN BREUS!", + "diagnosis_domain_expires_in": "{domain} expirarà en {days} dies.", + "diagnosis_swap_tip": "Vigileu i tingueu en compte que els servidor està allotjant memòria d'intercanvi en una targeta SD o en l'emmagatzematge SSD, això pot reduir dràsticament l'esperança de vida del dispositiu." } From 23e993af972de56a6ed7b8a0c19260ae37ee3df1 Mon Sep 17 00:00:00 2001 From: clecle226 Date: Sun, 10 May 2020 13:50:34 +0000 Subject: [PATCH 146/482] Translated using Weblate (French) Currently translated at 99.8% (637 of 638 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 ced8d92be..bd8197c2e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -333,7 +333,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 l'ensemble PHP pour utiliser PHP 7 au lieu de 5", + "migration_description_0004_php5_to_php7_pools": "Reconfigurez l'ensemble 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 est installé, mais pas PostgreSQL 9.6 ‽ Quelque chose de bizarre aurait pu se produire sur votre système :(…", From 6f03f72e256741361f3cae51a90fc7adcb8659f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quent=C3=AD?= Date: Mon, 18 May 2020 20:24:03 +0000 Subject: [PATCH 147/482] Translated using Weblate (Occitan) Currently translated at 58.3% (372 of 638 strings) Translation: YunoHost/core Translate-URL: https://translate.yunohost.org/projects/yunohost/core/oc/ --- locales/oc.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/oc.json b/locales/oc.json index cdefd0931..13572a1b1 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -578,5 +578,7 @@ "diagnosis_mail_ehlo_could_not_diagnose_details": "Error : {error}", "diagnosis_mail_queue_unavailable_details": "Error : {error}", "diagnosis_basesystem_hardware": "L’arquitectura del servidor es {virt} {arch}", - "diagnosis_basesystem_hardware_board": "Lo modèl de carta del servidor es {model}" + "diagnosis_basesystem_hardware_board": "Lo modèl de carta del servidor es {model}", + "backup_archive_corrupted": "Sembla que l’archiu de la salvagarda « {archive} » es corromput : {error}", + "diagnosis_domain_expires_in": "{domain} expiraà d’aquí {days} jorns." } From 4ec426c35bba4acfe11251309677611191924f89 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 19 May 2020 23:55:16 +0200 Subject: [PATCH 148/482] Small translation fix --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index bd8197c2e..b92c828a2 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -166,7 +166,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} n’est pas sur le point d’expirer ! (Vous pouvez utiliser --force si vous savez ce que vous faites)", - "certmanager_domain_http_not_working": "Le domaine {domain:s} ne semble pas être accessible via HTTP. Merci de vérifier la catégorie 'Web' dans le diagnostic pour plus d'informations. (Ou si vous savez ce que vous faites, utilisé '--no-checks' pour désactiver la vérification.)", + "certmanager_domain_http_not_working": "Le domaine {domain:s} ne semble pas être accessible via HTTP. Merci de vérifier la catégorie 'Web' dans le diagnostic pour plus d'informations. (Ou si vous savez ce que vous faites, utilisez '--no-checks' pour désactiver la vérification.)", "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 (des vérificateurs 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}", @@ -333,7 +333,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 l'ensemble PHP pour utiliser PHP 7 au lieu de PHP 5", + "migration_description_0004_php5_to_php7_pools": "Reconfigurer l'ensemble 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 est installé, mais pas PostgreSQL 9.6 ‽ Quelque chose de bizarre aurait pu se produire sur votre système :(…", From 1abdf16b84db314e52fd37cb341885870fe67650 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 20 May 2020 14:53:31 +0200 Subject: [PATCH 149/482] CI: Global refactor, yunohost-ci v2 ready --- .gitlab-ci.yml | 262 ++----------------------------- .gitlab/ci/build.gitlab-ci.yml | 52 ++++++ .gitlab/ci/install.gitlab-ci.yml | 16 ++ .gitlab/ci/lint.gitlab-ci.yml | 22 +++ .gitlab/ci/test.gitlab-ci.yml | 102 ++++++++++++ 5 files changed, 203 insertions(+), 251 deletions(-) create mode 100644 .gitlab/ci/build.gitlab-ci.yml create mode 100644 .gitlab/ci/install.gitlab-ci.yml create mode 100644 .gitlab/ci/lint.gitlab-ci.yml create mode 100644 .gitlab/ci/test.gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index befa66c1e..b4ee10cc3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,260 +1,20 @@ stages: - build - install - - postinstall - tests - lint -######################################## -# BUILD DEB -######################################## +default: + tags: + - yunohost-ci + # All jobs are interruptible by default + interruptible: true -.build-stage: - image: before-install - stage: build - variables: +variables: YNH_BUILD_DIR: "ynh-build" - YNH_SOURCE: "https://github.com/yunohost" - before_script: - - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" install git git-buildpackage postfix python-setuptools - - mkdir -p $YNH_BUILD_DIR - cache: - paths: - - $YNH_BUILD_DIR/*.deb - key: "$CI_PIPELINE_ID" -.build_script: &build_script | - cd $YNH_BUILD_DIR/$PACKAGE - VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null) - VERSION_NIGHTLY="${VERSION}~${CI_COMMIT_REF_SLUG//-}+$(date +%Y%m%d%H%M)" - dch --package "${PACKAGE}" --force-bad-version -v "${VERSION_NIGHTLY}" -D "unstable" --force-distribution "Daily build." - debuild -us -uc - -build-yunohost: - extends: .build-stage - variables: - PACKAGE: "yunohost" - script: - - git ls-files | xargs tar -czf archive.tar.gz - - mkdir -p $YNH_BUILD_DIR/$PACKAGE - - cat archive.tar.gz | tar -xz -C $YNH_BUILD_DIR/$PACKAGE - - rm archive.tar.gz - - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE - - *build_script - - -build-ssowat: - extends: .build-stage - variables: - PACKAGE: "ssowat" - script: - - git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE - - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE - - *build_script - -build-moulinette: - extends: .build-stage - variables: - PACKAGE: "moulinette" - script: - - git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE - - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE - - *build_script - -######################################## -# INSTALL DEB -######################################## - -install: - image: before-install - stage: install - variables: - YNH_BUILD_DIR: "ynh-build" - before_script: - - apt install --assume-yes wget debhelper - - echo "deb http://forge.yunohost.org/debian/ stretch stable testing unstable" > /etc/apt/sources.list.d/yunohost.list - - wget -O- https://forge.yunohost.org/yunohost.asc -q | apt-key add -qq - >/dev/null 2>&1 - - apt update - # https://github.com/YunoHost/install_script/blob/3e16abd7c4e1fe9c518cbc573282cb8fb1fcbbd7/install_yunohost#L433-L485 - - touch /var/log/auth.log - - > - if ! id avahi > /dev/null 2>&1; then - avahi_id=$((500 + RANDOM % 500)) - while cut -d ':' -f 3 /etc/passwd | grep -q $avahi_id - do - avahi_id=$((500 + RANDOM % 500)) - done - adduser --disabled-password --quiet --system --home /var/run/avahi-daemon --no-create-home --gecos "Avahi mDNS daemon" --group avahi --uid $avahi_id - fi - script: - - | - debconf-set-selections << EOF - slapd slapd/password1 password yunohost - slapd slapd/password2 password yunohost - slapd slapd/domain string yunohost.org - slapd shared/organization string yunohost.org - slapd slapd/allow_ldap_v2 boolean false - slapd slapd/invalid_config boolean true - slapd slapd/backend select MDB - postfix postfix/main_mailer_type select Internet Site - postfix postfix/mailname string /etc/mailname - mariadb-server-10.1 mysql-server/root_password password yunohost - mariadb-server-10.1 mysql-server/root_password_again password yunohost - nslcd nslcd/ldap-bindpw password - nslcd nslcd/ldap-starttls boolean false - nslcd nslcd/ldap-reqcert select - nslcd nslcd/ldap-uris string ldap://localhost/ - nslcd nslcd/ldap-binddn string - nslcd nslcd/ldap-base string dc=yunohost,dc=org - libnss-ldapd libnss-ldapd/nsswitch multiselect group, passwd, shadow - postsrsd postsrsd/domain string yunohost.org - EOF - - cd $YNH_BUILD_DIR - - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./*.deb - artifacts: - paths: - - $YNH_BUILD_DIR/*.deb - cache: - paths: - - $YNH_BUILD_DIR/ - policy: pull - key: "$CI_PIPELINE_ID" - -######################################## -# POSTINSTALL -######################################## - -postinstall: - image: before-postinstall - stage: postinstall - script: - - apt install --no-install-recommends -y $(cat debian/control | grep "^Depends" -A50 | grep "Recommends:" -B50 | grep "^ *," | grep -o -P "[\w\-]{3,}") - - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns - -######################################## -# TESTS -######################################## - -.test-stage: - image: after-postinstall - stage: tests - variables: - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - before_script: - - apt-get install python-pip -y - - pip install -U pip - - hash -d pip - - pip install pytest pytest-sugar pytest-mock requests-mock mock - - export PYTEST_ADDOPTS="--color=yes" - cache: - paths: - - .cache/pip - - src/yunohost/tests/apps - key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" - -root-tests: - extends: .test-stage - script: - - py.test tests - -test-apps: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_apps.py - -test-appscatalog: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_appscatalog.py - -test-appurl: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_appurl.py - -test-apps-arguments-parsing: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_apps_arguments_parsing.py - -test-backuprestore: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_backuprestore.py - -test-changeurl: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_changeurl.py - -test-permission: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_permission.py - -test-settings: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_settings.py - -test-user-group: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_user-group.py - -test-regenconf: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_regenconf.py - -test-service: - extends: .test-stage - script: - - cd src/yunohost - - py.test tests/test_service.py - -######################################## -# LINTER -######################################## - -.lint-stage: - image: before-postinstall - stage: lint - before_script: - - apt-get install python-pip -y - - mkdir -p .pip - - pip install -U pip - - hash -d pip - - pip --cache-dir=.pip install tox - cache: - paths: - - .pip - - .tox - key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" - -lint: - extends: .lint-stage - allow_failure: true - script: - - tox -e lint - -invalidcode: - extends: .lint-stage - script: - - tox -e invalidcode - -# Disabled, waiting for buster -#format-check: -# extends: .lint-stage -# script: -# - black --check --diff +include: + - local: .gitlab/ci/build.gitlab-ci.yml + - local: .gitlab/ci/install.gitlab-ci.yml + - local: .gitlab/ci/test.gitlab-ci.yml + - local: .gitlab/ci/lint.gitlab-ci.yml diff --git a/.gitlab/ci/build.gitlab-ci.yml b/.gitlab/ci/build.gitlab-ci.yml new file mode 100644 index 000000000..67232ba1f --- /dev/null +++ b/.gitlab/ci/build.gitlab-ci.yml @@ -0,0 +1,52 @@ +.build-stage: + stage: build + image: "before-install" + variables: + YNH_SOURCE: "https://github.com/yunohost" + before_script: + - mkdir -p $YNH_BUILD_DIR + artifacts: + paths: + - $YNH_BUILD_DIR/*.deb + +.build_script: &build_script + - cd $YNH_BUILD_DIR/$PACKAGE + - VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null) + - VERSION_NIGHTLY="${VERSION}+$(date +%Y%m%d%H%M)" + - dch --package "${PACKAGE}" --force-bad-version -v "${VERSION_NIGHTLY}" -D "unstable" --force-distribution "Daily build." + - debuild --no-lintian -us -uc + +######################################## +# BUILD DEB +######################################## + +build-yunohost: + extends: .build-stage + variables: + PACKAGE: "yunohost" + script: + - git ls-files | xargs tar -czf archive.tar.gz + - mkdir -p $YNH_BUILD_DIR/$PACKAGE + - cat archive.tar.gz | tar -xz -C $YNH_BUILD_DIR/$PACKAGE + - rm archive.tar.gz + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE + - *build_script + + +build-ssowat: + extends: .build-stage + variables: + PACKAGE: "ssowat" + script: + - git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE + - *build_script + +build-moulinette: + extends: .build-stage + variables: + PACKAGE: "moulinette" + script: + - git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE + - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE + - *build_script diff --git a/.gitlab/ci/install.gitlab-ci.yml b/.gitlab/ci/install.gitlab-ci.yml new file mode 100644 index 000000000..664fc66d5 --- /dev/null +++ b/.gitlab/ci/install.gitlab-ci.yml @@ -0,0 +1,16 @@ +######################################## +# INSTALL DEB +######################################## + +upgrade: + stage: install + image: "after-install" + script: + - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb + +install-postinstall: + stage: install + image: "before-install" + script: + - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb + - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns diff --git a/.gitlab/ci/lint.gitlab-ci.yml b/.gitlab/ci/lint.gitlab-ci.yml new file mode 100644 index 000000000..31f88dad1 --- /dev/null +++ b/.gitlab/ci/lint.gitlab-ci.yml @@ -0,0 +1,22 @@ +######################################## +# LINTER +######################################## + +lint: + stage: lint + image: "before-install" + allow_failure: true + script: + - tox -e lint + +invalidcode: + stage: lint + image: "before-install" + script: + - tox -e invalidcode + +# Disabled, waiting for buster +#format-check: +# extends: .lint-stage +# script: +# - black --check --diff diff --git a/.gitlab/ci/test.gitlab-ci.yml b/.gitlab/ci/test.gitlab-ci.yml new file mode 100644 index 000000000..dcc1b2d94 --- /dev/null +++ b/.gitlab/ci/test.gitlab-ci.yml @@ -0,0 +1,102 @@ +.install_debs: &install_debs + - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb + +.test-stage: + stage: tests + image: "after-install" + variables: + PYTEST_ADDOPTS: "--color=yes" + before_script: + - *install_debs + cache: + paths: + - src/yunohost/tests/apps + key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" + +######################################## +# TESTS +######################################## + +full-tests: + stage: tests + image: "before-install" + variables: + PYTEST_ADDOPTS: "--color=yes" + before_script: + - *install_debs + - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns + script: + - py.test tests + - cd src/yunohost + - py.test tests + +root-tests: + extends: .test-stage + script: + - py.test tests + +test-apps: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_apps.py + +test-appscatalog: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_appscatalog.py + +test-appurl: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_appurl.py + +test-apps-arguments-parsing: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_apps_arguments_parsing.py + +test-backuprestore: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_backuprestore.py + +test-changeurl: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_changeurl.py + +test-permission: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_permission.py + +test-settings: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_settings.py + +test-user-group: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_user-group.py + +test-regenconf: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_regenconf.py + +test-service: + extends: .test-stage + script: + - cd src/yunohost + - py.test tests/test_service.py From 7ad9fbd5b9de81aa8054e937cef092d850012ac9 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Wed, 20 May 2020 15:21:46 +0200 Subject: [PATCH 150/482] [fix] helper doc --- data/helpers.d/apt | 2 -- doc/helper_doc_template.html | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/data/helpers.d/apt b/data/helpers.d/apt index 74862eca5..c6621d814 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -353,8 +353,6 @@ ynh_remove_app_dependencies () { ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used. } -#================================================= - # Install packages from an extra repository properly. # # usage: ynh_install_extra_app_dependencies --repo="repo" --package="dep1 dep2" [--key=key_url] [--name=name] diff --git a/doc/helper_doc_template.html b/doc/helper_doc_template.html index 92611c737..e5fec733c 100644 --- a/doc/helper_doc_template.html +++ b/doc/helper_doc_template.html @@ -2,6 +2,8 @@

App helpers

+

Doc auto-generated by this script on {{data.date}} (Yunohost version {{data.version}})

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

{{ category }}

@@ -81,9 +83,6 @@ {% endfor %} {% endfor %} -

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

- -